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.
2.3.1. Añadiendo la lógica en la ruta personalizada
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.
Nota 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 elhost
contiene un subdominio que pertenezca a alguno de los objetosClient
. - Si se cumple el patrón de la ruta, se devuelve un parámetro adicional llamado
client_id
, obtenido del objetoClient
y que se añade al resto de parámetros de la petición.
2.3.2. Haciendo uso de la ruta propia
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);
}
Nota 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
.
Nota 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.
2.3.3. Generando la ruta correcta
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.
Nota 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.