# The Standard Router

## Introduction

Zend_Controller_Router_Rewrite is the standard framework router. Routing is the process of taking a URI endpoint (that part of the URI which comes after the base URL) and decomposing it into parameters to determine which module, controller, and action of that controller should receive the request. This values of the module, controller, action and other parameters are packaged into a Zend_Controller_Request_Http object which is then processed by Zend_Controller_Dispatcher_Standard. Routing occurs only once: when the request is initially received and before the first controller is dispatched.

Zend_Controller_Router_Rewrite is designed to allow for mod_rewrite-like functionality using pure PHP structures. It is very loosely based on Ruby on Rails routing and does not require any prior knowledge of webserver URL rewriting. It is designed to work with a single Apache mod_rewrite rule (one of):

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

or (preferred):

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

The rewrite router can also be used with the IIS webserver (versions <= 7.0) if Isapi_Rewrite has been installed as an Isapi extension with the following rewrite rule:

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

IIS Isapi_Rewrite

When using IIS, `$_SERVER['REQUEST_URI']` will either not exist, or be set as an empty string. In this case, Zend_Controller_Request_Http will attempt to use the `$_SERVER['HTTP_X_REWRITE_URL']` value set by the Isapi_Rewrite extension.

IIS 7.0 introduces a native URL rewriting module, and it can be configured as follows:

    <?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>

If using Lighttpd, the following rewrite rule is valid:

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

## Using a Router

To properly use the rewrite router you have to instantiate it, add some user defined routes and inject it into the controller. The following code illustrates the procedure:

    // 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'))
    );

## Basic Rewrite Router Operation

The heart of the RewriteRouter is the definition of user defined routes. Routes are added by calling the addRoute method of RewriteRouter and passing in a new instance of a class implementing Zend_Controller_Router_Route_Interface. Eg.:

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

Rewrite Router comes with six basic types of routes (one of which is special):

- Zend_Controller_Router_Route

- Zend_Controller_Router_Route_Static

- Zend_Controller_Router_Route_Regex

- Zend_Controller_Router_Route_Hostname

- Zend_Controller_Router_Route_Chain

- Zend_Controller_Router_Rewrite *

Routes may be used numerous times to create a chain or user defined application routing schema. You may use any number of routes in any configuration, with the exception of the Module route, which should rather be used once and probably as the most generic route (i.e., as a default). Each route will be described in greater detail later on.

The first parameter to addRoute is the name of the route. It is used as a handle for getting the routes out of the router (e.g., for URL generation purposes). The second parameter being the route itself.

The most common use of the route name is through the means of Zend_View url helper:

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

Which would result in the href: `user/martel`.

Routing is a simple process of iterating through all provided routes and matching its definitions to current request URI. When a positive match is found, variable values are returned from the Route instance and are injected into the Zend_Controller_Request object for later use in the dispatcher as well as in user created controllers. On a negative match result, the next route in the chain is checked.

If you need to determine which route was matched, you can use the getCurrentRouteName() method, which will return the identifier used when registering the route with the router. If you want the actual route object, you can use getCurrentRoute().

Reverse Matching

Routes are matched in reverse order so make sure your most generic routes are defined first.

Returned Values

Values returned from routing come from URL parameters or user defined route defaults. These variables are later accessible through the Zend_Controller_Request::getParam() or Zend_Controller_Action::_getParam() methods.

There are three special variables which can be used in your routes - 'module', 'controller' and 'action'. These special variables are used by Zend_Controller_Dispatcher to find a controller and action to dispatch to.

Special Variables

The names of these special variables may be different if you choose to alter the defaults in Zend_Controller_Request_Http by means of the setControllerKey() and setActionKey() methods.

## Default Routes

Zend_Controller_Router_Rewrite comes preconfigured with a default route, which will match URIs in the shape of `controller/action`. Additionally, a module name may be specified as the first path element, allowing URIs of the form `module/controller/action`. Finally, it will also match any additional parameters appended to the URI by default - `controller/action/var1/value1/var2/value2`.

Some examples of how such routes are matched:

    // 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

The default route is simply a Zend_Controller_Router_Route_Module object stored under the name (index) of 'default' in RewriteRouter. It's created more-or-less like below:

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

If you do not want this particular default route in your routing schema, you may override it by creating your own 'default' route (i.e., storing it under the name of 'default') or removing it altogether by using removeDefaultRoutes():

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

## Base URL and Subdirectories

The rewrite router can be used in subdirectories (e.g., `http://domain.com/user/application-root/`) in which case the base URL of the application (`/user/application-root`) should be automatically detected by Zend_Controller_Request_Http and used accordingly.

Should the base URL be detected incorrectly you can override it with your own base path by using Zend_Controller_Request_Http and calling the setBaseUrl() method (see Base URL and Subdirectories):

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

## Global Parameters

You can set global parameters in a router which are automatically supplied to a route when assembling through setGlobalParam(). If a global parameter is set but also given to the assemble method directly, the user parameter overrides the global parameter. You can set a global parameter this way:

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

## Route Types

## Using Zend_Config with the RewriteRouter

Sometimes it is more convenient to update a configuration file with new routes than to change the code. This is possible via the addConfig() method. Basically, you create a Zend_Config-compatible configuration, and in your code read it in and pass it to the RewriteRouter.

As an example, consider the following INI file:

    [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

The above INI file can then be read into a Zend_Config object as follows:

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

In the above example, we tell the router to use the 'routes' section of the INI file to use for its routes. Each first-level key under that section will be used to define a route name; the above example defines the routes 'archive' and 'news'. Each route then requires, at minimum, a 'route' entry and one or more 'defaults' entries; optionally one or more 'reqs' (short for 'required') may be provided. All told, these correspond to the three arguments provided to a Zend_Controller_Router_Route_Interface object. An option key, 'type', can be used to specify the route class type to use for that particular route; by default, it uses Zend_Controller_Router_Route. In the example above, the 'news' route is defined to use Zend_Controller_Router_Route_Static.

## Subclassing the Router

The standard rewrite router should provide most functionality you may need; most often, you will only need to create a new route type in order to provide new or modified functionality over the provided routes.

That said, you may at some point find yourself wanting to use a different routing paradigm. The interface Zend_Controller_Router_Interface provides the minimal information required to create a router, and consists of a single method.

    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);
    }

Routing only occurs once: when the request is first received into the system. The purpose of the router is to determine the controller, action, and optional parameters based on the request environment, and then set them in the request. The request object is then passed to the dispatcher. If it is not possible to map a route to a dispatch token, the router should do nothing to the request object.