válido para Symfony 1.3 y 1.4
Índice de contenidos
- Enrutamiento avanzado (primera parte)
- Preparación el proyecto: un CMS para muchos clientes
- Cómo funciona el sistema de enrutamiento
- Creando una clase de ruta personalizada
A continuación se crea una nueva clase de ruta para extender la ruta page_show
de forma que tenga en cuenta el subdominio de los objetos Client. Para ello,
crea un archivo llamado acClientObjectRoute.class.php en el directorio
lib/routing del proyecto (debes crear este directorio manualmente):
// lib/routing/acClientObjectRoute.class.php class acClientObjectRoute extends sfDoctrineRoute { public function matchesUrl($url, $context = array()) { if (false === $parameters = parent::matchesUrl($url, $context)) { return false; } return $parameters; } }
El único paso que falta es indicar a la ruta page_show que utilice esta nueva
clase de ruta. Actualiza el valor de la clave class de la ruta en el archivo
routing.yml:
# apps/fo/config/routing.yml
page_show:
url: /:slug
class: acClientObjectRoute
options:
model: Page
type: object
params:
module: page
action: show
Aunque el uso de la clase acClientObjectRoute todavía no añade ninguna
funcionalidad, la aplicación ya está preparada para funcionar como se espera.
El método matchesUrl() se encarga principalmente de dos tareas.
Para que la ruta propia incluya la funcionalidad requerida, reemplaza los
contenidos del archivo acClientObjectRoute.class.php por lo siguiente.
class acClientObjectRoute extends sfDoctrineRoute { protected $baseHost = '.sympalbuilder.com'; public function matchesUrl($url, $context = array()) { if (false === $parameters = parent::matchesUrl($url, $context)) { return false; } // devuelve false si no se encuentra el valor de "baseHost" if (strpos($context['host'], $this->baseHost) === false) { return false; } $subdomain = str_replace($this->baseHost, '', $context['host']); $client = Doctrine_Core::getTable('Client') ->findOneBySubdomain($subdomain) ; if (!$client) { return false; } return array_merge(array('client_id' => $client->id), $parameters); } }
La llamada inicial al método parent::matchesUrl() es importante porque ejecuta
el proceso normal de comprobación de las rutas. En este ejemplo, como la URL
/location cumple con el patrón de la ruta page_show, el método
parent::matchesUrl() devolvería un array que contiene el parámetro slug.
array('slug' => 'location')
En otras palabras, el trabajo duro del enrutamiento se realiza de forma
automática, por lo que el resto del método se puede dedicar a obtener el objeto
Client correcto para ese subdominio.
public function matchesUrl($url, $context = array()) { // ... $subdomain = str_replace($this->baseHost, '', $context['host']); $client = Doctrine_Core::getTable('Client') ->findOneBySubdomain($subdomain) ; if (!$client) { return false; } return array_merge(array('client_id' => $client->id), $parameters); }
Realizando una sustitución en la cadena de texto se puede obtener la parte del
subdominio del host y después realizar una consulta en la base de datos para
determinar si algún objeto Client tiene este subdominio. Si no existen objetos
Client con ese subdominio, se devuelve el valor false para indicar que la
petición entrante no cumple con el patrón de esta ruta. Si por el contrario
existe un objeto Client con ese subdominio, se añade un nuevo parámetro
llamado client_id en el array que se devuelve.
El array $context que se pasa a matchesUrl() incluye mucha información
útil sobre la petición actual, incluyendo el host, un valor booleano que
indica si la petición es segura (is_secure), la URI de la petición (request_uri),
el método de HTTP (method) y mucho más.
¿Qué es lo que se ha conseguido con esta ruta personalizada? Básicamente la
clase acClientObjectRoute ahora realiza lo siguiente:
La $url entrante sólo cumplirá el patrón de la ruta si el host contiene
un subdominio que pertenezca a alguno de los objetos Client.
Si se cumple el patrón de la ruta, se devuelve un parámetro adicional llamado
client_id, obtenido del objeto Client y que se añade al resto de
parámetros de la petición.
Una vez que acClientObjectRoute devuelve el parámetro client_id correcto,
la acción puede obtenerlo a través del objeto de la petición. La acción
page/show podría utilizar por ejemplo el parámetro client_id para encontrar
el objeto Page correcto:
public function executeShow(sfWebRequest $request) { $this->page = Doctrine_Core::getTable('Page')->findOneBySlugAndClientId( $request->getParameter('slug'), $request->getParameter('client_id') ); $this->forward404Unless($this->page); }
El método findOneBySlugAndClientId() es un nuevo tipo de
buscador mágico
de Doctrine 1.2 que busca objetos en función de varios campos.
El framework de enrutamiento permite aplicar una solución todavía más elegante.
En primer lugar, añade el siguiente método a la clase acClientObjectRoute:
protected function getRealVariables() { return array_merge(array('client_id'), parent::getRealVariables()); }
Gracias a este último método, la acción puede obtener el objeto Page correcto
directamente desde la ruta. Por tanto, la acción page/show se puede reducir
a una única línea de código.
public function executeShow(sfWebRequest $request) { $this->page = $this->getRoute()->getObject(); }
Sin necesidad de añadir más código, la instrucción anterior busca un objeto de
tipo Page en función de las columnas slug y client_id. Además, al igual
que el resto de rutas de objetos, la acción redirige de forma automática a la
página del error 404 si no se encuentra ningún objeto.
¿Cómo funciona? Las rutas de objetos, como sfDoctrineRoute, utilizada por la
clase acClientObjectRoute, busca automáticamente el objeto relacionado en
función de las variables de la clave url de la ruta. La ruta page_show por
ejemplo contiene la variable :slug en su url, por lo que busca el objeto
Page mediante el valor de la columna slug.
No obstante, en esta aplicación la ruta page_show también debe buscar los
objetos Page en función de la columna client_id. Para ello, se ha redefinido
el método sfObjectRoute::getRealVariables(), que se invoca internamente
para obtener las columnas con las que se realiza la consulta. Añadiendo el campo
client_id en este array, acClientObjectRoute buscará los objetos haciendo
uso de las columnas slug y client_id.
Las rutas de objetos ignoran automáticamente cualquier variable que no se
corresponda a una columna real. Si por ejemplo la URL contiene una variable
llamada :page pero la tabla no contiene una columna page, esta variable se
ignora.
A estas alturas, ya hemos conseguido que la clase de ruta propia realice todo lo necesario. En las próximas secciones se reutiliza esta nueva ruta para crear un área de administración específico para cada cliente.
Aún existe un pequeño problema sobre cómo se genera la ruta. Imagina que se crea un enlace a una página utilizando el siguiente código:
<?php echo link_to('Locations', 'page_show', $page) ?>
URL generada: /location?client_id=1
Como puedes observar, el valor de client_id se ha añadido automáticamente al
final de la URL. Esto sucede porque la ruta trata de utilizar todas sus variables
para generar la URL. Como la ruta dispone de un parámetro llamado slug y de
otro parámetro llamado client_id, hace uso de los dos al generar la ruta.
Para solucionarlo, añade el siguiente método a la clase acClientObjectRoute:
protected function doConvertObjectToArray($object) { $parameters = parent::doConvertObjectToArray($object); unset($parameters['client_id']); return $parameters; }
Cuando se genera una ruta de objetos, se obtiene toda la información necesaria
invocando el método doConvertObjectToArray(). Por defecto se devuelve client_id
en el array $parameters. Al eliminar esa variable, se evita que se incluya
en la URL generada. Recuerda que esto es posible porque la información del
objeto Client se guarda en el propio subdominio.
Puedes redefinir completamente el proceso de doConvertObjectToArray() y
gestionarlo tu mismo añadiendo un método llamado toParams() en la clase del
modelo. Este método debe devolver un array con los parámetros que quieres que
se utilicen al generar la ruta.
válido para Symfony 1.3 y 1.4