TigerZF
🌐Español

24.5. El enrutador estándar

24.5.1. Introducción

Zend_Controller_Router_Rewrite es el enrutador estándar del framework. El enrutamiento es el proceso de tomar un endpoint de URI (la parte de la URI que viene después de la URL base) y descomponerlo en parámetros para determinar qué módulo, controlador y acción de ese controlador deben recibir la petición. Los valores del módulo, controlador, acción y otros parámetros se empaquetan en un objeto Zend_Controller_Request_Http que luego es procesado por Zend_Controller_Dispatcher_Standard. El enrutamiento ocurre solo una vez: cuando la petición es recibida inicialmente y antes de que se despache el primer controlador.

Zend_Controller_Router_Rewrite está diseñado para permitir una funcionalidad similar a mod_rewrite usando estructuras PHP puras. Está muy vagamente basado en el enrutamiento de Ruby on Rails y no requiere ningún conocimiento previo de la reescritura de URL del servidor web. Está diseñado para funcionar con una única regla de Apache mod_rewrite (una de):

RewriteEngine on
RewriteRule !\.(js|ico|gif|jpg|png|css|html)$ index.php

o (preferida):

RewriteEngine On
RewriteCond %{REQUEST_FILENAME} -s [OR]
RewriteCond %{REQUEST_FILENAME} -l [OR]
RewriteCond %{REQUEST_FILENAME} -d
RewriteRule ^.*$ - [NC,L]
RewriteRule ^.*$ index.php [NC,L]

El enrutador de reescritura también puede usarse con el servidor web IIS (versiones <= 7.0) si se ha instalado Isapi_Rewrite como una extensión Isapi con la siguiente regla de reescritura:

RewriteRule ^[\w/\%]*(?:\.(?!(?:js|ico|gif|jpg|png|css|html)$)[\w\%]*$)? /index.php [I]
[Note] IIS Isapi_Rewrite

Al usar IIS, $_SERVER['REQUEST_URI'] no existirá, o se establecerá como una cadena vacía. En este caso, Zend_Controller_Request_Http intentará usar el valor $_SERVER['HTTP_X_REWRITE_URL'] establecido por la extensión Isapi_Rewrite.

IIS 7.0 introduce un módulo nativo de reescritura de URL, y puede configurarse de la siguiente manera:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
     <system.webServer>
         <rewrite>
             <rules>
                 <rule name="Imported Rule 1" stopProcessing="true">
                     <match url="^.*$" />
                     <conditions logicalGrouping="MatchAny">
                         <add input="{REQUEST_FILENAME}"
                             matchType="IsFile" pattern=""
                             ignoreCase="false" />
                         <add input="{REQUEST_FILENAME}"
                             matchType="IsDirectory"
                             pattern="" ignoreCase="false" />
                     </conditions>
                     <action type="None" />
                 </rule>
                 <rule name="Imported Rule 2" stopProcessing="true">
                     <match url="^.*$" />
                     <action type="Rewrite" url="index.php" />
                 </rule>
             </rules>
         </rewrite>
     </system.webServer>
</configuration>

Si se usa Lighttpd, la siguiente regla de reescritura es válida:

url.rewrite-once = (
    ".*\?(.*)$" => "/index.php?$1",
    ".*\.(js|ico|gif|jpg|png|css|html)$" => "$0",
    "" => "/index.php"
)

24.5.2. Usando un enrutador

Para usar correctamente el enrutador de reescritura hay que instanciarlo, añadir algunas rutas definidas por el usuario e inyectarlo en el controlador. El siguiente código ilustra el procedimiento:

// Create a router

$router = $ctrl->getRouter(); // returns a rewrite router by default
$router->addRoute(
    'user',
    new Zend_Controller_Router_Route('user/:username',
                                     array('controller' => 'user',
                                           'action' => 'info'))
);

24.5.3. Funcionamiento básico del enrutador de reescritura

El corazón del RewriteRouter es la definición de rutas definidas por el usuario. Las rutas se añaden llamando al método addRoute de RewriteRouter y pasando una nueva instancia de una clase que implementa Zend_Controller_Router_Route_Interface. P. ej.:

$router->addRoute('user',
                  new Zend_Controller_Router_Route('user/:username'));

Rewrite Router viene con seis tipos básicos de rutas (uno de los cuales es especial):

Las rutas pueden usarse numerosas veces para crear una cadena o un esquema de enrutamiento de aplicación definido por el usuario. Puede usar cualquier número de rutas en cualquier configuración, con la excepción de la ruta Module, que debería usarse una única vez y probablemente como la ruta más genérica (es decir, como predeterminada). Cada ruta se describirá con mayor detalle más adelante.

El primer parámetro de addRoute es el nombre de la ruta. Se usa como identificador para obtener las rutas del enrutador (por ejemplo, con fines de generación de URL). El segundo parámetro es la propia ruta.

[Note] Nota

El uso más común del nombre de la ruta es a través del helper url de Zend_View:

<a href=
"<?php echo $this->url(array('username' => 'martel'), 'user') ?>">Martel</a>

Lo que resultaría en el href: user/martel.

El enrutamiento es un proceso simple de iterar a través de todas las rutas proporcionadas y comparar sus definiciones con la URI de la petición actual. Cuando se encuentra una coincidencia positiva, se devuelven los valores de las variables desde la instancia de Route y se inyectan en el objeto Zend_Controller_Request para su uso posterior en el dispatcher, así como en los controladores creados por el usuario. En caso de un resultado de coincidencia negativa, se comprueba la siguiente ruta en la cadena.

Si necesita determinar qué ruta coincidió, puede usar el método getCurrentRouteName(), que devolverá el identificador usado al registrar la ruta con el enrutador. Si desea el objeto de ruta real, puede usar getCurrentRoute().

[Note] Coincidencia inversa

Las rutas se comparan en orden inverso, así que asegúrese de que sus rutas más genéricas se definan primero.

[Note] Valores devueltos

Los valores devueltos por el enrutamiento provienen de parámetros de URL o de valores predeterminados de rutas definidos por el usuario. Estas variables son luego accesibles a través de los métodos Zend_Controller_Request::getParam() o Zend_Controller_Action::_getParam().

Hay tres variables especiales que pueden usarse en sus rutas: 'module', 'controller' y 'action'. Estas variables especiales son usadas por Zend_Controller_Dispatcher para encontrar un controlador y una acción a la que despachar.

[Note] Variables especiales

Los nombres de estas variables especiales pueden ser diferentes si opta por alterar los valores predeterminados en Zend_Controller_Request_Http mediante los métodos setControllerKey() y setActionKey().

24.5.4. Rutas predeterminadas

Zend_Controller_Router_Rewrite viene preconfigurado con una ruta predeterminada, que coincidirá con URIs con la forma de controller/action. Además, se puede especificar un nombre de módulo como el primer elemento de la ruta, permitiendo URIs de la forma module/controller/action. Finalmente, también coincidirá con cualquier parámetro adicional añadido a la URI por defecto - controller/action/var1/value1/var2/value2.

Algunos ejemplos de cómo coinciden dichas rutas:

// Assuming the following:
$ctrl->setControllerDirectory(
    array(
        'default' => '/path/to/default/controllers',
        'news'    => '/path/to/news/controllers',
        'blog'    => '/path/to/blog/controllers'
    )
);

Module only:
http://example/news
    module == news

Invalid module maps to controller name:
http://example/foo
    controller == foo

Module + controller:
http://example/blog/archive
    module     == blog
    controller == archive

Module + controller + action:
http://example/blog/archive/list
    module     == blog
    controller == archive
    action     == list

Module + controller + action + params:
http://example/blog/archive/list/sort/alpha/date/desc
    module     == blog
    controller == archive
    action     == list
    sort       == alpha
    date       == desc

La ruta predeterminada es simplemente un objeto Zend_Controller_Router_Route_Module almacenado bajo el nombre (índice) 'default' en RewriteRouter. Se crea más o menos como sigue:

$compat = new Zend_Controller_Router_Route_Module(array(),
                                                  $dispatcher,
                                                  $request);
$this->addRoute('default', $compat);

Si no desea esta ruta predeterminada particular en su esquema de enrutamiento, puede sobrescribirla creando su propia ruta 'default' (es decir, almacenándola bajo el nombre 'default') o eliminándola por completo usando removeDefaultRoutes():

// Remove any default routes
$router->removeDefaultRoutes();

24.5.5. URL base y subdirectorios

El enrutador de reescritura puede usarse en subdirectorios (por ejemplo, http://domain.com/user/application-root/), en cuyo caso la URL base de la aplicación (/user/application-root) debería ser detectada automáticamente por Zend_Controller_Request_Http y usada en consecuencia.

Si la URL base se detecta incorrectamente, puede sobrescribirla con su propia ruta base usando Zend_Controller_Request_Http y llamando al método setBaseUrl() (vea URL base y subdirectorios):

$request->setBaseUrl('/~user/application-root/');

24.5.6. Parámetros globales

Puede establecer parámetros globales en un enrutador que se suministran automáticamente a una ruta al ensamblar mediante setGlobalParam(). Si se establece un parámetro global pero también se proporciona directamente al método assemble, el parámetro del usuario sobrescribe al parámetro global. Puede establecer un parámetro global de esta manera:

$router->setGlobalParam('lang', 'en');

24.5.7. Tipos de rutas

24.5.7.1. Zend_Controller_Router_Route

Zend_Controller_Router_Route es la ruta estándar del framework. Combina la facilidad de uso con una definición de ruta flexible. Cada ruta consiste principalmente en un mapeo de URL (de partes estáticas y dinámicas (variables)) y puede inicializarse con valores predeterminados así como con requisitos de variables.

Imaginemos que nuestra aplicación ficticia necesitará una página informativa sobre los autores del contenido. Queremos poder apuntar nuestros navegadores web a http://domain.com/author/martel para ver la información sobre este tipo llamado "martel". Y la ruta para tal funcionalidad podría verse así:

$route = new Zend_Controller_Router_Route(
    'author/:username',
    array(
        'controller' => 'profile',
        'action'     => 'userinfo'
    )
);

$router->addRoute('user', $route);

El primer parámetro en el constructor de Zend_Controller_Router_Route es una definición de ruta que se comparará con una URL. Las definiciones de ruta constan de partes estáticas y dinámicas separadas por el carácter de barra ('/'). Las partes estáticas son solo texto simple: author. Las partes dinámicas, llamadas variables, se marcan anteponiendo dos puntos al nombre de la variable: :username.

[Note] Uso de caracteres

La implementación actual permite usar cualquier carácter (excepto una barra) como identificador de variable, pero se recomienda encarecidamente usar solo caracteres que sean válidos para identificadores de variables PHP. Implementaciones futuras pueden alterar este comportamiento, lo que podría resultar en errores ocultos en su código.

Este ejemplo de ruta debería coincidir cuando apunte su navegador a http://domain.com/author/martel, en cuyo caso todas sus variables se inyectarán en el objeto Zend_Controller_Request y serán accesibles en su ProfileController. Las variables devueltas por este ejemplo pueden representarse como un array de los siguientes pares clave y valor:

$values = array(
    'username'   => 'martel',
    'controller' => 'profile',
    'action'     => 'userinfo'
);

Más adelante, Zend_Controller_Dispatcher_Standard debería invocar el método userinfoAction() de su clase ProfileController (en el módulo predeterminado) basándose en estos valores. Allí podrá acceder a todas las variables mediante los métodos Zend_Controller_Action::_getParam() o Zend_Controller_Request::getParam():

public function userinfoAction()
{
    $request = $this->getRequest();
    $username = $request->getParam('username');

    $username = $this->_getParam('username');
}

La definición de ruta puede contener un carácter especial más: un comodín, representado por el símbolo '*'. Se usa para recopilar parámetros de manera similar a la ruta Module predeterminada (pares var => value definidos en la URI). La siguiente ruta imita más o menos el comportamiento de la ruta Module:

$route = new Zend_Controller_Router_Route(
    ':module/:controller/:action/*',
    array('module' => 'default')
);
$router->addRoute('default', $route);
24.5.7.1.1. Valores predeterminados de variables

Cada variable en la ruta puede tener un valor predeterminado y para eso se usa el segundo parámetro del constructor de Zend_Controller_Router_Route. Este parámetro es un array con claves que representan nombres de variables y con valores como los predeterminados deseados:

$route = new Zend_Controller_Router_Route(
    'archive/:year',
    array('year' => 2006)
);
$router->addRoute('archive', $route);

La ruta anterior coincidirá con URLs como http://domain.com/archive/2005 y http://example.com/archive. En el último caso la variable year tendrá un valor predeterminado inicial de 2006.

Este ejemplo resultará en la inyección de una variable year en el objeto request. Dado que no hay información de enrutamiento presente (no se definen parámetros de controller y action), la aplicación se despachará al controlador y método de acción predeterminados (ambos definidos en Zend_Controller_Dispatcher_Abstract). Para hacerlo más útil, debe proporcionar un controller y una action válidos como valores predeterminados de la ruta:

$route = new Zend_Controller_Router_Route(
    'archive/:year',
    array(
        'year'       => 2006,
        'controller' => 'archive',
        'action'     => 'show'
    )
);
$router->addRoute('archive', $route);

Esta ruta resultará entonces en el despacho al método showAction() de la clase ArchiveController.

24.5.7.1.2. Requisitos de variables

Se puede añadir un tercer parámetro al constructor de Zend_Controller_Router_Route donde pueden establecerse los requisitos de las variables. Estos se definen como partes de una expresión regular:

$route = new Zend_Controller_Router_Route(
    'archive/:year',
    array(
        'year'       => 2006,
        'controller' => 'archive',
        'action'     => 'show'
    ),
    array('year' => '\d+')
);
$router->addRoute('archive', $route);

Con una ruta definida como la anterior, el enrutador la hará coincidir solo cuando la variable year contenga datos numéricos, por ejemplo http://domain.com/archive/2345. Una URL como http://example.com/archive/test no coincidirá y el control pasará a la siguiente ruta de la cadena en su lugar.

24.5.7.1.3. Segmentos traducidos

La ruta estándar admite segmentos traducidos. Para usar esta característica, debe definir al menos un traductor (una instancia de Zend_Translate) mediante una de las siguientes formas:

  • Colocarlo en el registro con la clave Zend_Translate.

  • Establecerlo mediante el método estático Zend_Controller_Router_Route::setDefaultTranslator().

  • Pasarlo como cuarto parámetro al constructor.

Por defecto, se usará el locale especificado en la instancia de Zend_Translate. Para sobrescribirlo, se establece (una instancia de Zend_Locale o una cadena de locale) de una de las siguientes maneras:

  • Colocarlo en el registro con la clave Zend_Locale.

  • Establecerlo mediante el método estático Zend_Controller_Router_Route::setDefaultLocale().

  • Pasarlo como quinto parámetro al constructor.

  • Pasarlo como parámetro @locale al método assemble.

Los segmentos traducidos se dividen en dos partes. Los segmentos fijos se prefijan con un único signo @, y se traducirán al locale actual al ensamblar y se revertirán al ID del mensaje al volver a coincidir. Los segmentos dinámicos se prefijan con :@. Al ensamblar, el parámetro dado se traducirá y se insertará en la posición del parámetro. Al coincidir, el parámetro traducido de la URL se revertirá al ID del mensaje nuevamente.

[Note] IDs de mensaje y archivo de idioma separado

Ocasionalmente un ID de mensaje que desea usar en una de sus rutas ya se usa en un script de vista o en algún otro lugar. Para tener control total sobre URLs seguras, debería usar un archivo de idioma separado para los mensajes usados en la ruta.

La siguiente es la forma más simple de preparar la ruta estándar para el uso de segmentos traducidos:

// Prepare the translator
$translator = new Zend_Translate(
    array(
        'adapter' => 'array',
        'content' => array(),
        'locale'  => 'en'
    )
);
$translator->addTranslation(
    array(
        'content' =>
            array(
                'archive' => 'archiv',
                'year'    => 'jahr',
                'month'   => 'monat',
                'index'   => 'uebersicht'
            ),
        'locale'  => 'de'
    )
);

// Set the current locale for the translator
$translator->setLocale('en');

// Set it as default translator for routes
Zend_Controller_Router_Route::setDefaultTranslator($translator);

Este ejemplo demuestra el uso de segmentos estáticos:

// Create the route
$route = new Zend_Controller_Router_Route(
    '@archive',
    array(
        'controller' => 'archive',
        'action'     => 'index'
    )
);
$router->addRoute('archive', $route);

// Assemble the URL in default locale: archive
$route->assemble(array());

// Assemble the URL in german: archiv
$route->assemble(array());

Puede usar los segmentos dinámicos para crear una versión traducida similar a una ruta module:

// Create the route
$route = new Zend_Controller_Router_Route(
    ':@controller/:@action/*',
    array(
        'controller' => 'index',
        'action'     => 'index'
    )
);
$router->addRoute('archive', $route);

// Assemble the URL in default locale: archive/index/foo/bar
$route->assemble(array('controller' => 'archive', 'foo' => 'bar'));

// Assemble the URL in german: archiv/uebersicht/foo/bar
$route->assemble(array('controller' => 'archive', 'foo' => 'bar'));

También puede mezclar segmentos estáticos y dinámicos:

// Create the route
$route = new Zend_Controller_Router_Route(
    '@archive/:@mode/:value',
    array(
        'mode'       => 'year'
        'value'      => 2005,
        'controller' => 'archive',
        'action'     => 'show'
    ),
    array('mode'  => '(month|year)'
          'value' => '\d+')
);
$router->addRoute('archive', $route);

// Assemble the URL in default locale: archive/month/5
$route->assemble(array('mode' => 'month', 'value' => '5'));

// Assemble the URL in german: archiv/monat/5
$route->assemble(array('mode' => 'month', 'value' => '5', '@locale' => 'de'));

24.5.7.2. Zend_Controller_Router_Route_Static

Los ejemplos anteriores usan todos rutas dinámicas: rutas que contienen patrones con los que comparar. Sin embargo, a veces una ruta en particular está fijada de antemano, y poner en marcha el motor de expresiones regulares sería excesivo. La respuesta a esta situación es usar rutas estáticas:

$route = new Zend_Controller_Router_Route_Static(
    'login',
    array('controller' => 'auth', 'action' => 'login')
);
$router->addRoute('login', $route);

La ruta anterior coincidirá con una URL de http://domain.com/login, y despachará a AuthController::loginAction().

[Note] Advertencia: las rutas estáticas deben contener valores predeterminados razonables

Dado que una ruta estática no pasa ninguna parte de la URL al objeto request como parámetros, debe pasar todos los parámetros necesarios para despachar una petición como valores predeterminados a la ruta. Omitir los valores predeterminados "controller" o "action" tendrá resultados inesperados, y probablemente resultará en que la petición no pueda despacharse.

Como regla general, siempre proporcione cada uno de los siguientes valores predeterminados:

  • controller

  • action

  • module (si no es el predeterminado)

Opcionalmente, también puede pasar el parámetro "useDefaultControllerAlways" al controlador frontal durante el arranque:

$front->setParam('useDefaultControllerAlways', true);

Sin embargo, esto se considera una solución alternativa; siempre es mejor definir explícitamente valores predeterminados razonables.

24.5.7.3. Zend_Controller_Router_Route_Regex

Además de los tipos de ruta default y static, hay disponible un tipo de ruta de Expresión Regular. Esta ruta ofrece más potencia y flexibilidad que las otras, pero a un ligero costo de complejidad. Al mismo tiempo, debería ser más rápida que la ruta Route estándar.

Al igual que la ruta estándar, esta ruta tiene que inicializarse con una definición de ruta y algunos valores predeterminados. Creemos una ruta archive como ejemplo, similar a la definida anteriormente, solo que usando esta vez la ruta Regex:

$route = new Zend_Controller_Router_Route_Regex(
    'archive/(\d+)',
    array(
        'controller' => 'archive',
        'action'     => 'show'
    )
);
$router->addRoute('archive', $route);

Cada subpatrón de regex definido se inyectará en el objeto request. Con nuestro ejemplo anterior, después de una coincidencia exitosa con http://domain.com/archive/2006, el array de valores resultante podría verse así:

$values = array(
    1            => '2006',
    'controller' => 'archive',
    'action'     => 'show'
);
[Note] Nota

Las barras iniciales y finales se recortan de la URL en el Router antes de una coincidencia. Como resultado, hacer coincidir la URL http://domain.com/foo/bar/, implicaría una regex de foo/bar, y no /foo/bar.

[Note] Nota

Los anclajes de inicio y fin de línea ('^' y '$', respectivamente) se anteponen y añaden automáticamente a todas las expresiones. Por lo tanto, no debería usarlos en sus expresiones regulares, y debería hacer coincidir la cadena completa.

[Note] Nota

Esta clase de ruta usa el carácter '#' como delimitador. Esto significa que necesitará escapar los caracteres de almohadilla ('#') pero no las barras diagonales ('/') en sus definiciones de ruta. Dado que el carácter '#' (ancla nombrada) rara vez se pasa al servidor web, rara vez necesitará usar ese carácter en su regex.

Puede obtener el contenido de los subpatrones definidos de la manera habitual:

public function showAction()
{
    $request = $this->getRequest();
    $year    = $request->getParam(1); // $year = '2006';
}
[Note] Nota

Note que la clave es un entero (1) en lugar de una cadena ('1').

Esta ruta aún no funcionará exactamente igual que su contraparte de ruta estándar, ya que el valor predeterminado para 'year' aún no está establecido. Y lo que puede no ser aún evidente es que tendremos un problema con una barra final incluso si declaramos un valor predeterminado para el año y hacemos que el subpatrón sea opcional. La solución es hacer que toda la parte del año sea opcional junto con la barra, pero capturar solo la parte numérica:

$route = new Zend_Controller_Router_Route_Regex(
    'archive(?:/(\d+))?',
    array(
        1            => '2006',
        'controller' => 'archive',
        'action'     => 'show'
    )
);
$router->addRoute('archive', $route);

Ahora lleguemos al problema que probablemente ya haya notado por su cuenta. Usar claves basadas en enteros para los parámetros no es una solución fácilmente manejable y puede ser potencialmente problemática a largo plazo. Y ahí es donde entra el tercer parámetro. Este parámetro es un array asociativo que representa un mapa de subpatrones de regex a claves de parámetros con nombre. Trabajemos en nuestro ejemplo más fácil:

$route = new Zend_Controller_Router_Route_Regex(
    'archive/(\d+)',
    array(
        'controller' => 'archive',
        'action' => 'show'
    ),
    array(
        1 => 'year'
    )
);
$router->addRoute('archive', $route);

Esto resultará en los siguientes valores inyectados en Request:

$values = array(
    'year'       => '2006',
    'controller' => 'archive',
    'action'     => 'show'
);

El mapa puede definirse en cualquier dirección para que funcione en cualquier entorno. Las claves pueden contener nombres de variables o índices de subpatrones:

$route = new Zend_Controller_Router_Route_Regex(
    'archive/(\d+)',
    array( ... ),
    array(1 => 'year')
);

// OR

$route = new Zend_Controller_Router_Route_Regex(
    'archive/(\d+)',
    array( ... ),
    array('year' => 1)
);
[Note] Nota

Las claves de subpatrón deben representarse mediante enteros.

Note que el índice numérico en los valores de Request ahora ha desaparecido y una variable con nombre se muestra en su lugar. Por supuesto, puede mezclar variables numéricas y con nombre si lo desea:

$route = new Zend_Controller_Router_Route_Regex(
    'archive/(\d+)/page/(\d+)',
    array( ... ),
    array('year' => 1)
);

Lo que resultará en valores mixtos disponibles en Request. Como ejemplo, la URL http://domain.com/archive/2006/page/10 resultará en los siguientes valores:

$values = array(
    'year'       => '2006',
    2            => 10,
    'controller' => 'archive',
    'action'     => 'show'
);

Dado que los patrones regex no son fácilmente reversibles, necesitará preparar una URL inversa si desea usar un helper de URL o incluso un método assemble de esta clase. Esta ruta inversa está representada por una cadena analizable por sprintf() y se define como un cuarto parámetro del constructor:

$route = new Zend_Controller_Router_Route_Regex(
    'archive/(\d+)',
    array( ... ),
    array('year' => 1),
    'archive/%s'
);

Todo esto era algo ya posible mediante un objeto de ruta estándar, entonces ¿dónde está el beneficio de usar la ruta Regex, se preguntará? Principalmente, permite describir cualquier tipo de URL sin restricciones. Imagine que tiene un blog y desea crear URLs como: http://domain.com/blog/archive/01-Using_the_Regex_Router.html, y que descomponga el último elemento de la ruta, 01-Using_the_Regex_Router.html, en un ID de artículo y un título o descripción del artículo; esto no es posible con la ruta estándar. Con la ruta Regex, puede hacer algo como la siguiente solución:

$route = new Zend_Controller_Router_Route_Regex(
    'blog/archive/(\d+)-(.+)\.html',
    array(
        'controller' => 'blog',
        'action'     => 'view'
    ),
    array(
        1 => 'id',
        2 => 'description'
    ),
    'blog/archive/%d-%s.html'
);
$router->addRoute('blogArchive', $route);

Como puede ver, esto añade una tremenda cantidad de flexibilidad sobre la ruta estándar.

24.5.7.4. Zend_Controller_Router_Route_Hostname

Zend_Controller_Router_Route_Hostname es la ruta de hostname del framework. Funciona de manera similar a la ruta estándar, pero opera con el hostname de la URL llamada en lugar de con la ruta.

Usemos el ejemplo de la ruta estándar y veamos cómo se vería de una manera basada en el hostname. En lugar de llamar al usuario a través de una ruta, queremos que un usuario pueda llamar a http://martel.users.example.com para ver la información sobre el usuario "martel":

$hostnameRoute = new Zend_Controller_Router_Route_Hostname(
    ':username.users.example.com',
    array(
        'controller' => 'profile',
        'action'     => 'userinfo'
    )
);

$plainPathRoute = new Zend_Controller_Router_Route_Static('');

$router->addRoute('user', $hostnameRoute->chain($plainPathRoute));

El primer parámetro en el constructor de Zend_Controller_Router_Route_Hostname es una definición de ruta que se comparará con un hostname. Las definiciones de ruta constan de partes estáticas y dinámicas separadas por el carácter de punto ('.'). Las partes dinámicas, llamadas variables, se marcan anteponiendo dos puntos al nombre de la variable: :username. Las partes estáticas son solo texto simple: user.

Las rutas de hostname pueden, pero nunca deberían, usarse tal cual. La razón detrás de esto es que una ruta de hostname sola coincidiría con cualquier ruta. Así que lo que debe hacer es encadenar una ruta de path a la ruta de hostname. Esto se hace como en el ejemplo llamando a $hostnameRoute->chain($pathRoute);. Al hacer esto, $hostnameRoute no se modifica, sino que se devuelve una nueva ruta (Zend_Controller_Router_Route_Chain), que luego puede darse al enrutador.

24.5.7.5. Zend_Controller_Router_Route_Chain

Zend_Controller_Router_Route_Chain es una ruta que permite encadenar múltiples rutas juntas. Esto le permite encadenar rutas de hostname y rutas de ruta, o múltiples rutas de ruta, por ejemplo. El encadenamiento puede hacerse ya sea programáticamente o dentro de un archivo de configuración.

[Note] Prioridad de parámetros

Al encadenar rutas juntas, los parámetros de la ruta externa tienen mayor prioridad que los parámetros de la ruta interna. Así, si define un controller en la ruta externa y en la interna, se seleccionará el controller de la ruta externa.

Al encadenar programáticamente, hay dos formas de lograrlo. La primera es crear una nueva instancia de Zend_Controller_Router_Route_Chain y luego llamar al método chain() varias veces con todas las rutas que deben encadenarse juntas. La otra forma es tomar la primera ruta, por ejemplo, una ruta de hostname, y llamar al método chain() en ella con la ruta que debe añadirse. Esto no modificará la ruta de hostname, sino que devolverá una nueva instancia de Zend_Controller_Router_Route_Chain, que entonces tiene ambas rutas encadenadas juntas:

// Create two routes
$hostnameRoute = new Zend_Controller_Router_Route_Hostname(...);
$pathRoute     = new Zend_Controller_Router_Route(...);

// First way, chain them via the chain route
$chainedRoute = new Zend_Controller_Router_Route_Chain();
$chainedRoute->chain($hostnameRoute)
             ->chain($pathRoute);

// Second way, chain them directly
$chainedRoute = $hostnameRoute->chain($pathRoute);

Al encadenar rutas juntas, su separador es una barra por defecto. Puede haber casos en los que desee tener un separador diferente:

// Create two routes
$firstRoute  = new Zend_Controller_Router_Route('foo');
$secondRoute = new Zend_Controller_Router_Route('bar');

// Chain them together with a different separator
$chainedRoute = $firstRoute->chain($secondRoute, '-');

// Assemble the route: "foo-bar"
echo $chainedRoute->assemble();
24.5.7.5.1. Encadenar rutas mediante Zend_Config

Para encadenar rutas juntas en un archivo de configuración, hay parámetros adicionales para la configuración de estas. El enfoque más simple es usar el parámetro chains. Este es simplemente una lista de rutas, que se encadenarán con la ruta padre. Ni la ruta padre ni la ruta hija se añadirán directamente al enrutador, solo la ruta encadenada resultante. El nombre de la ruta encadenada en el enrutador será el nombre de la ruta padre y el nombre de la ruta hija concatenados con un guion (-) por defecto. Una configuración simple en XML se vería así:

<routes>
    <www type="Zend_Controller_Router_Route_Hostname">
        <route>www.example.com</route>
        <chains>
            <language type="Zend_Controller_Router_Route">
                <route>:language</route>
                <reqs language="[a-z]{2}">
                <chains>
                    <index type="Zend_Controller_Router_Route_Static">
                        <route></route>
                        <defaults module="default" controller="index"
                                  action="index" />
                    </index>
                    <imprint type="Zend_Controller_Router_Route_Static">
                        <route>imprint</route>
                        <defaults module="default" controller="index"
                                  action="index" />
                    </imprint>
                </chains>
            </language>
        </chains>
    </www>
    <users type="Zend_Controller_Router_Route_Hostname">
        <route>users.example.com</route>
        <chains>
            <profile type="Zend_Controller_Router_Route">
                <route>:username</route>
                <defaults module="users" controller="profile" action="index" />
            </profile>
        </chains>
    </users>
    <misc type="Zend_Controller_Router_Route_Static">
        <route>misc</route>
    </misc>
</routes>

Esto resultará en las tres rutas www-language-index, www-language-imprint y users-language-profile que solo coincidirán en función del hostname y la ruta misc, que coincidirá con cualquier hostname.

La forma alternativa de crear una ruta encadenada es mediante el parámetro chain, que solo puede usarse directamente con el tipo de ruta chain, y que también solo funciona en el nivel raíz:

<routes>
    <www type="Zend_Controller_Router_Route_Chain">
        <route>www.example.com</route>
    </www>
    <language type="Zend_Controller_Router_Route">
        <route>:language</route>
        <reqs language="[a-z]{2}">
    </language>
    <index type="Zend_Controller_Router_Route_Static">
        <route></route>
        <defaults module="default" controller="index" action="index" />
    </index>
    <imprint type="Zend_Controller_Router_Route_Static">
        <route>imprint</route>
        <defaults module="default" controller="index" action="index" />
    </imprint>

    <www-index type="Zend_Controller_Router_Route_Chain">
        <chain>www, language, index</chain>
    </www-index>
    <www-imprint type="Zend_Controller_Router_Route_Chain">
        <chain>www, language, imprint</chain>
    </www-imprint>
</routes>

También puede dar el parámetro chain como array en lugar de separar las rutas con una coma:

<routes>
    <www-index type="Zend_Controller_Router_Route_Chain">
        <chain>www</chain>
        <chain>language</chain>
        <chain>index</chain>
    </www-index>
    <www-imprint type="Zend_Controller_Router_Route_Chain">
        <chain>www</chain>
        <chain>language</chain>
        <chain>imprint</chain>
    </www-imprint>
</routes>

Cuando configure rutas chain con Zend_Config y desee que el separador de nombre de la cadena sea diferente de un guion, necesita especificar este separador por separado:

$config = new Zend_Config(array(
    'chainName' => array(
        'type'   => 'Zend_Controller_Router_Route_Static',
        'route'  => 'foo',
        'chains' => array(
            'subRouteName' => array(
                'type'     => 'Zend_Controller_Router_Route_Static',
                'route'    => 'bar',
                'defaults' => array(
                    'module'      => 'module',
                     'controller' => 'controller',
                     'action'     => 'action'
                )
            )
        )
    )
));

// Set separator before adding config
$router->setChainNameSeparator('_separator_')

// Add config
$router->addConfig($config);

// The name of our route now is: chainName_separator_subRouteName
echo $this->_router->assemble(array(), 'chainName_separator_subRouteName');

// The proof: it echoes /foo/bar

24.5.7.6. Zend_Rest_Route

El componente Zend_Rest contiene una ruta RESTful para Zend_Controller_Router_Rewrite. Esta ruta ofrece un esquema de enrutamiento estandarizado que enruta las peticiones traduciendo el método HTTP y la URI a un módulo, controlador y acción. La tabla a continuación ofrece una visión general de cómo se enrutan los métodos de petición y las URIs.

Tabla 24.1. Comportamiento de Zend_Rest_Route

Método URI Module_Controller::action
GET /product/ratings/ Product_RatingsController::indexAction()
GET /product/ratings/:id Product_RatingsController::getAction()
POST /product/ratings Product_RatingsController::postAction()
PUT /product/ratings/:id Product_RatingsController::putAction()
DELETE /product/ratings/:id Product_RatingsController::deleteAction()
POST /product/ratings/:id?_method=PUT Product_RatingsController::putAction()
POST /product/ratings/:id?_method=DELETE Product_RatingsController::deleteAction()

24.5.7.6.1. Uso de Zend_Rest_Route

Para habilitar Zend_Rest_Route para toda una aplicación, constrúyala sin parámetros de configuración y añádala como la ruta predeterminada en el controlador frontal:

$front     = Zend_Controller_Front::getInstance();
$restRoute = new Zend_Rest_Route($front);
$front->getRouter()->addRoute('default', $restRoute);
[Note] Nota

Si Zend_Rest_Route no puede hacer coincidir un módulo, controlador o acción válidos, devolverá FALSE y el enrutador intentará coincidir usando la siguiente ruta en el enrutador.

Para habilitar Zend_Rest_Route para módulos específicos, constrúyala con un array de nombres de módulos como el tercer argumento del constructor:

$front     = Zend_Controller_Front::getInstance();
$restRoute = new Zend_Rest_Route($front, array(), array('product'));
$front->getRouter()->addRoute('rest', $restRoute);

Para habilitar Zend_Rest_Route para controladores específicos, añada un array de nombres de controladores como el valor de cada elemento del array de módulo.

$front     = Zend_Controller_Front::getInstance();
$restRoute = new Zend_Rest_Route($front, array(), array(
    'product' => array('ratings')
));
$front->getRouter()->addRoute('rest', $restRoute);
24.5.7.6.2. Zend_Rest_Route con Zend_Config_Ini

Para usar Zend_Rest_Route desde un archivo de configuración INI, use un parámetro de tipo de ruta y establezca las opciones de configuración:

routes.rest.type = Zend_Rest_Route
routes.rest.defaults.controller = object
routes.rest.mod = project,user

La opción 'type' designa el tipo de configuración de enrutamiento RESTful. La opción 'defaults' se usa para especificar módulo, controlador y/o acciones predeterminados personalizados para la ruta. Todas las demás opciones en el grupo de configuración se tratan como nombres de módulos RESTful, y sus valores son nombres de controladores RESTful. El ejemplo de configuración define Mod_ProjectController y Mod_UserController como controladores RESTful.

Luego use el método addConfig() del objeto router Rewrite:

$config = new Zend_Config_Ini('path/to/routes.ini');
$router = new Zend_Controller_Router_Rewrite();
$router->addConfig($config, 'routes');
24.5.7.6.3. Zend_Rest_Controller

Para ayudar o guiar el desarrollo de controladores para su uso con Zend_Rest_Route, haga que sus controladores extiendan de Zend_Rest_Controller. Zend_Rest_Controller define las 5 operaciones más comúnmente necesarias para recursos RESTful en forma de métodos de acción abstractos.

  • indexAction() - Debería recuperar un índice de recursos y asignarlo a la vista.

  • getAction() - Debería recuperar un único recurso identificado por URI y asignarlo a la vista.

  • postAction() - Debería aceptar un nuevo recurso único y persistir su estado.

  • putAction() - Debería aceptar un único recurso identificado por URI y persistir su estado.

  • deleteAction() - Debería eliminar un único recurso identificado por URI.

24.5.8. Usar Zend_Config con el RewriteRouter

A veces es más conveniente actualizar un archivo de configuración con nuevas rutas que cambiar el código. Esto es posible mediante el método addConfig(). Básicamente, se crea una configuración compatible con Zend_Config, y en su código se lee y se pasa al RewriteRouter.

Como ejemplo, considere el siguiente archivo INI:

[production]
routes.archive.route = "archive/:year/*"
routes.archive.defaults.controller = archive
routes.archive.defaults.action = show
routes.archive.defaults.year = 2000
routes.archive.reqs.year = "\d+"

routes.news.type = "Zend_Controller_Router_Route_Static"
routes.news.route = "news"
routes.news.defaults.controller = "news"
routes.news.defaults.action = "list"

routes.archive.type = "Zend_Controller_Router_Route_Regex"
routes.archive.route = "archive/(\d+)"
routes.archive.defaults.controller = "archive"
routes.archive.defaults.action = "show"
routes.archive.map.1 = "year"
; OR: routes.archive.map.year = 1

El archivo INI anterior puede leerse entonces en un objeto Zend_Config de la siguiente manera:

$config = new Zend_Config_Ini('/path/to/config.ini', 'production');
$router = new Zend_Controller_Router_Rewrite();
$router->addConfig($config, 'routes');

En el ejemplo anterior, le decimos al enrutador que use la sección 'routes' del archivo INI para sus rutas. Cada clave de primer nivel bajo esa sección se usará para definir un nombre de ruta; el ejemplo anterior define las rutas 'archive' y 'news'. Cada ruta requiere entonces, como mínimo, una entrada 'route' y una o más entradas 'defaults'; opcionalmente se pueden proporcionar una o más 'reqs' (abreviatura de 'required'). En conjunto, estas corresponden a los tres argumentos proporcionados a un objeto Zend_Controller_Router_Route_Interface. Una clave de opción, 'type', puede usarse para especificar el tipo de clase de ruta a usar para esa ruta en particular; por defecto, usa Zend_Controller_Router_Route. En el ejemplo anterior, la ruta 'news' se define para usar Zend_Controller_Router_Route_Static.

24.5.9. Subclasificar el enrutador

El enrutador de reescritura estándar debería proporcionar la mayor parte de la funcionalidad que pueda necesitar; la mayoría de las veces, solo necesitará crear un nuevo tipo de ruta para proporcionar una funcionalidad nueva o modificada sobre las rutas provistas.

Dicho esto, puede que en algún momento se encuentre queriendo usar un paradigma de enrutamiento diferente. La interfaz Zend_Controller_Router_Interface proporciona la información mínima requerida para crear un enrutador, y consiste en un único método.

interface Zend_Controller_Router_Interface
{
  /**
   * @param  Zend_Controller_Request_Abstract $request
   * @throws Zend_Controller_Router_Exception
   * @return Zend_Controller_Request_Abstract
   */
  public function route(Zend_Controller_Request_Abstract $request);
}

El enrutamiento ocurre solo una vez: cuando la petición se recibe por primera vez en el sistema. El propósito del enrutador es determinar el controlador, la acción y los parámetros opcionales en función del entorno de la petición, y luego establecerlos en la petición. El objeto de petición se pasa entonces al dispatcher. Si no es posible mapear una ruta a un token de despacho, el enrutador no debería hacer nada con el objeto de petición.