7.4. Provided Subclasses

7.4.1. Introduction

Zend Framework provides several alternatives to the default classes provided, including alternate request objects, routers, and response objects.

7.4.2. Zend_Controller_Request_Http

7.4.2.1. Introduction

Zend_Controller_Request_Http provides a request object for use in an HTTP environment. Zend_Controller_Request_Http is the default request class utilised by Zend_Controller_Dispatcher.

7.4.2.2. Accessing Request Data

Zend_Controller_Request_Http encapsulates access to relevant values such as the key name and value for the controller and action router variables, and all additional parameters parsed from the URI. By proxying to Zend_Controller_Request_Http it additionally allows access to values contained in the superglobals as public members and manages the current Base URL and Request URI. Superglobal values cannot be set on a request object, instead use the setParam/getParam methods to set or retrieve user parameters.

[Note] Superglobal data

When accessing superglobal data through Zend_Controller_Request_Http as public member properties, it is necessary to keep in mind that the property name (superglobal array key) is matched to a superglobal in a specific order of precedence: 1. GET, 2. POST, 3. COOKIE, 4. SERVER, 5. ENV.

Specific superglobals can be accessed using a public method as an alternative. For example, the raw value of $_POST['user'] can be accessed by calling getPost('user') on the request object.

7.4.2.3. Base Url and subdirectories

Zend_Controller_Request_Http allows Zend_Controller_Router_Rewrite to be used in subdirectories. Zend_Controller_Request_Http will attempt to automatically detect your base URL and set it accordingly.

For example, if you keep your index.php in a webserver subdirectory named /projects/myapp/index.php, base URL (rewrite base) should be set to /projects/myapp. This string will then be stripped from the beginning of the path before calculating any route matches. This frees one from the necessity of prepending it to any of your routes. A route of 'user/:username' will match URIs like http://localhost/projects/myapp/user/martel and http://example.com/user/martel.

[Note] URL detection is case sensitive

Automatic base URL detection is case sensitive, so make sure your URL will match a subdirectory name in a filesystem (even on Windows machines). If it doesn't you will get to noRoute action.

Should base URL be detected incorrectly you can override it with your own base path with the help of the setBaseUrl() method of either the Zend_Controller_Request_Http class, or the Zend_Controller_Front class. The easiest method is to set it in Zend_Controller_Front, which will proxy it into the request object. Example usage to set a custom base URL:

/**
 * Dispatch Request with custom base URL with Zend_Controller_Front.
 */
$router     = new Zend_Controller_Router_Rewrite();
$controller = Zend_Controller_Front::getInstance();
$controller->setControllerDirectory('./application/controllers')
           ->setRouter($router)
           ->setBaseUrl('/projects/myapp'); // set the base url!
$response   = $controller->dispatch();

7.4.3. Zend_Controller_Router_Rewrite

7.4.3.1. 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 controller and action of that controller should receive the request. This value of the controller, action and other parameters is 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)$ index.php

or:

RewriteEngine on
RewriteCond %{SCRIPT_FILENAME} !-f
RewriteCond %{SCRIPT_FILENAME} !-d
RewriteRule ^(.*)$ index.php/$1 

The rewrite router can also be used with the IIS webserver if Isapi_Rewrite has been installed as an Isapi extension with the following rewrite rule:

RewriteRule ^[\w/\%]*(?:\.(?!(?:js|ico|gif|jpg|png|css)$)[\w\%]*$)? /index.php [I]
[Note] 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.

If using Lighttpd, the following rewrite rule is valid:

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

7.4.3.2. 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'))
);

7.4.3.3. Basic Routes

The heart of the RewriteRouter is the definition of user defined routes. Routes are created by calling the addRoute method of RewriteRouter and passing in a new instance of Zend_Controller_Router_Route:

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

The first parameter is the name of the route. It is redundant at the moment of writing but will be used in the future in a URL view helper to allow for easy URL generation in your views. Should you need to make use of a previously configured named route, you can retrieve it with the getRoute method of the RewriteRouter. The second parameter is an instance of Zend_Controller_Router_Route.

The first parameter for the Zend_Controller_Router_Route constructor is a route that will be matched to a URL - for example, the above route will match http://example.com/user/martel. The colon in a route marks a URL variable. After the successful routing, values of all defined variables will be injected to the Zend_Controller_Request. After that they will be accessible through a Zend_Controller_Request::getParam or Zend_Controller_Action::_getParam methods. In our example a parameter named username will be set to a value of 'martel'.

[Note] Reverse matching

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

[Note] Character useage

For now the current implementation allows for use of any characters except a slash (/) as a variable identifier but it is strongly recommended that one uses only php variable friendly characters. In future the implementation will probably be altered and this may introduce bugs to your code.

There are two special variables which can be used in your routes - ':controller' and ':action'. These special variables will be used to find a controller and/or an action chosen in the URL. The ':action' variable must always be defined either in the route or as a default parameter. The ':controller' variable will default to the IndexController if it is not defined.

[Note] Special variables

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

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

If you point your browser to 'http://example.com/news/latest' with this route defined the Zend_Controller_Dispatcher will invoke the latestAction method of your NewsController class.

7.4.3.4. Variable defaults

Every variable in the route can have a default. To provide it you have to add a second parameter to the Zend_Controller_Router_Route constructor. This parameter is an array with keys as variable names and values as desired defaults.

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

What may not be clearly visible is that the above route will match URLs like 'http://example.com/archive/2005' and 'http://example.com/archive'. In the latter case the variable year will have a value of 2006.

The above example will only result in injecting a year variable to the request. No routing will take place since controller and action parameters are not set. To make it more usable you have to provide a valid controller and a valid action as defaults.

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

This route will then result in dispatching to showAction of ArchiveController.

7.4.3.5. Variable requirements

One can add a third parameter to the Zend_Controller_Router_Route constructor where variable requirements can be set. These are defined as regular expressions:

$router->addRoute(
    'archive',
    new Zend_Controller_Router_Route('archive/:year', array('year' => 2006), array('year' => '\d+'))
);
[Note] Routing behavior

Unlike Ruby on Rails, ZF RewriteRouter will match a route and use a default when a third parameter variable requirement is not met. So the URL of 'http://example.com/archive/test' will match the above route and set the year to 2006. This functionality may be altered in the future as it is being discussed at the moment of writing of this documentation.

7.4.3.6. Base URL and subdirectories

The rewrite router can be used in subdirectories, and the base URL will be automatically detected by Zend_Controller_Request_Http.

Should the base URL be detected incorrectly you can override it with your own base path with the help of Zend_Controller_Request_Http by calling the setBaseUrl() method (see Section 7.4.2.3, “Base Url and subdirectories”).

7.4.3.7. Default routes

Zend_Controller_Router_Rewrite is preconfigured with one default. It 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.

As some examples of how such routes are matched:

// Assuming the following:
// $ctrl->setControllerDirectory(array(
//     'default' => '/path/to/default/controllers',
//     'news'    => '/path/to/blog/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 a Zend_Controller_Router_Route_Module object instantiated without any defaults:

// Route for Router v1 compatibility
$compat = new Zend_Controller_Router_Route_Module();
$this->addRoute('default', $compat);
[Note] Matching URIs

Zend_Controller_Router_Rewrite is configured for backwards compatibility. It will automatically match controller/action URIs with additional parameters. The additional parameters do not require the addition of new routes unless they must have default values or variable requirements. These additional parameters will be accessible from the Zend_Controller_Action::_getParam() method.

If you do not want the default route in your routing schema, you may remove it using removeDefaultRoutes():

// Remove default compatability route
$router->removeDefaultRoutes();

7.4.3.8. Static Routes

The examples above all use dynamic routes -- routes that contain patterns to match against. Sometimes, however, a particular route is set in stone, and firing up the regular expression engine would be overkill. The answer to this situation is to use static routes:

$loginRoute = new Zend_Controller_Router_Route_Static('login', array('controller' => 'login', 'action' => 'form'));
$router->addRoute('login', $static);

7.4.3.9. Using Zend_Config with the RewriteRouter

Sometimes its 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:

/**
 * Example INI located at /path/to/config.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"
 */
$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, minimally 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.

7.4.4. Zend_Controller_Response_Http

Zend_Controller_Response_Http is a response object suitable for use in an HTTP environment. It contains methods for setting, retrieving, and clearing headers, and the __toString() method sends all headers at once before returning the response content.

setHeader() takes two arguments, a header type and the header value. A third, optional parameter, if passed and true, will force the new header to replace any other headers registered with that type.

7.4.5. Zend_Controller_Response_Cli

Zend_Controller_Response_Cli is a response object suitable for use in an CLI environment. It has no methods for handling headers, and simply returns all body content when __toString() is invoked.