Chapter 7. Zend_Controller

Table of Contents

7.1. Overview
7.1.1. Introduction
7.1.2. Request Object
7.1.3. Route Process
7.1.4. Dispatch Process
7.1.5. Response Object
7.2. Getting Started
7.2.1. Introduction
7.2.2. Server Configuration
7.2.3. Bootstrap File
7.2.4. Directory Structure
7.2.5. Default Controller
7.3. Subclassing
7.3.1. Introduction
7.3.2. Conventions
7.3.3. Front Controller
7.3.4. Request Abstract
7.3.5. Router Interface
7.3.6. Dispatcher Interface
7.3.7. Action Controller
7.3.8. Response Object
7.4. Provided Subclasses
7.4.1. Introduction
7.4.2. Zend_Controller_Request_Http
7.4.3. Zend_Controller_Router_Rewrite
7.4.4. Zend_Controller_Response_Http
7.4.5. Zend_Controller_Response_Cli
7.5. Action Controllers
7.5.1. Introduction
7.5.2. Object initialization
7.5.3. Pre- and Post-Dispatch Hooks
7.5.4. Accessors
7.5.5. Utility Methods
7.6. Plugins
7.6.1. Introduction
7.6.2. Writing Plugins
7.6.3. Using Plugins
7.7. Using a Conventional Modular Directory Structure
7.7.1. Introduction
7.7.2. Specifying Module Controller Directories
7.7.3. Routing to modules
7.7.4. Module or Global Default Controller
7.8. MVC Exceptions
7.8.1. Introduction
7.8.2. How can you handle exceptions?
7.8.3. MVC Exceptions You May Encounter
7.9. Migrating from Previous Versions
7.9.1. Migrating from 0.6.0 to 0.8.0
7.9.2. Migrating from 0.2.0 or before to 0.6.0

7.1. Overview

7.1.1. Introduction

Zend_Controller provides the foundation for building a website based on the Model-View-Controller (MVC) pattern.

The Zend_Controller system is designed to be lightweight, modular, and extensible. It is a minimalist design to permit flexibility and some freedom to users while providing enough structure so that systems built around Zend_Controller share some common conventions and similar code layout.

The Zend_Controller workflow is implemented by several components. While it is not necessary to completely understand the underpinnings of all of these components to use the system, having a working knowledge of the process is helpful.

  • Zend_Controller_Front orchestrates the entire workflow of the Zend_Controller system. It is an interpretation of the FrontController pattern. Zend_Controller_Front processes all requests received by the server and is ultimately responsible for delegating requests to ActionControllers (Zend_Controller_Action).

  • Zend_Controller_Request_Abstract represents the request environment and provides methods for setting and retrieving the controller and action names and any request parameters. Additionally it keeps track of whether or not the action it contains has been dispatched by Zend_Controller_Dispatcher. Extensions to the abstract request object can be used to encapsulate the entire request environment, allowing routers to pull information from the request environment in order to set the controller and action names.

    By default, Zend_Controller_Request_Http is used, which provides access to the entire HTTP request environment.

  • Zend_Controller_Router_Interface is used to define routers. Routing is the process of examining the request environment to determine which controller, and action of that controller, should receive the request. This controller, action, and optional parameters are then set in the request object to be processed by Zend_Controller_Dispatcher_Standard. Routing occurs only once: when the request is initially received and before the first controller is dispatched.

    The default router is Zend_Controller_Router_Rewrite.

    The default router, Zend_Controller_Router_Rewrite, takes a URI endpoint as specified in Zend_Controller_Request_Http and decomposes it into a controller, action, and parameters based on the path information in the url. As an example, the URL http://localhost/foo/bar/key/value would be decoded to use the foo controller, bar action, and specify a parameter key with a value of value.

    Zend_Controller_Router_Rewrite can also be used to match arbitrary paths; see the Rewrite Router documentation for more information.

  • Zend_Controller_Dispatcher_Interface is used to define dispatchers. Dispatching is the process of pulling the controller and action from the request object and mapping them to a controller file/class and action method in the controller class. If the controller or action do not exist, it handles determining default controllers and actions to dispatch.

    The actual dispatching process consists of instantiating the controller class and calling the action method in that class. Unlike routing, which occurs only once, dispatching occurs in a loop. If the request object's dispatched status is reset at any point, the loop will be repeated, calling whatever action is currently set in the request object. The first time the loop finishes with the request object's dispatched status set (boolean true), it will finish processing.

    The default dispatcher is Zend_Controller_Dispatcher_Standard. It defines controllers as CamelCasedClasses ending in the word Controller, and action methods as camelCasedMethods ending in the word Action: SomeFooController::barAction. In this case, the controller would be referred to as somefoo and the action as bar.

  • Zend_Controller_Action is the base controller component. Each controller is a single class that extends the Zend_Controller_Action class, and this class has action methods.

  • Zend_Controller_Response_Abstract defines a base response class used to collect and return responses from the action controllers. It collects both headers and body content, and, because it implements __toString(), can be directly echoed in order to send all headers and content at once.

    The default response class is Zend_Controller_Response_Http, which is suitable for use in an HTTP environment.

The workflow of Zend_Controller is relatively simple. A request is received by Zend_Controller_Front, which in turn calls Zend_Controller_Router_Rewrite to determine which controller (and action in that controller) to dispatch. Zend_Controller_Router_Rewrite decomposes the URI in order to set the controller and action names in the request. Zend_Controller_Front then enters a dispatch loop. It calls Zend_Controller_Dispatcher_Standard, passing it the request, to dispatch to the controller and action specified in the request (or use defaults). After the controller has finished, control returns to Zend_Controller_Front. If the controller has indicated that another controller should be dispatched by resetting the dispatched status of the request, the loop continues and another dispatch is performed. Otherwise, the process ends.

7.1.2. Request Object

The request object is a simple value object that is passed between Zend_Controller_Front and the router, dispatcher, and controller classes. It packages a definition of a controller, an action, and parameters to be passed to the action, as well as the rest of the request environment, be it HTTP, the CLI, or PHP-GTK.

  • The controller name is accessed by getControllerName() and setControllerName().

  • The name of the action to call within that controller is accessed by getActionName() and setActionName().

  • Parameters to be passed to that action are an associative array of key/value pairs that are accessed by getParams() and setParams(), or individually by getParam() and setParam().

Based on the type of request, there may be more methods available. The default request used, Zend_Controller_Request_Http, for instance, has methods for retrieving the request URI, path information, $_GET and $_POST parameters, etc.

The request object is passed to the front controller, or if none is provided, instantiated at the beginning of the dispatch process, before routing occurs. It is passed through to every object in the dispatch chain.

Additionally, the request object is particularly useful in testing. The developer may craft the request environment, including controller, action, parameters, URI, etc, and pass the request object to the front controller to test application flow. When paired with the response object, elaborate and precise unit testing of MVC applications becomes possible.

7.1.3. Route Process

Before your first controller can be built, you need to understand how the routing process works as it is implemented in Zend_Controller_Router_Rewrite. Remember that the workflow is divided into routing, which occurs only once, and dispatching, which occurs thereafter in a loop.

Zend_Controller_Front calls Zend_Controller_Router_Rewrite (or another registered router) to map a URI to a controller -- and an action within that controller. Zend_Controller_Router_Rewrite retrieves the URI from the request object and passes it to the Route objects in its chain; by default, it uses Zend_Controller_Router_Route_Module to match incoming URLs. The route object then decomposes the URL to determine the controller, action, and any other URL parameters passed in the path; the router itself then sets these in the request object.

Zend_Controller_Router_Route_Module uses a very simple mapping to determine the name of the controller and the name of the action within that controller:

http://framework.zend.com/controller/action/
        

Notice above that the first segment is always the name of the controller and the second segment is always the name of the action.

Optionally, parameters may be defined in the URI that will be passed to the controller. These take the form of key/value pairs:

http://framework.zend.com/controller/action/key1/value1/
        

If either the controller or action are missing from the URI path, Zend_Controller_Dispatcher_Standard will try and grab the value from the request object's parameters, and, if not found, use default values. In both cases, the default values are "index". These examples illustrate:

http://framework.zend.com/roadmap/future/
Controller: roadmap
Action    : future

http://framework.zend.com/roadmap/
Controller: roadmap
Action    : index

http://framework.zend.com/
Controller: index
Action    : index
        
[Note] Flexibility

If you want more flexible capabilities, you may want to check out the Rewrite Router documentation.

The controller name, the action name within that controller, and any optional parameters are set in the request object. When Zend_Controller_Front enters the dispatch loop, the request object will be passed to Zend_Controller_Dispatcher_Standard.

7.1.4. Dispatch Process

Dispatching is the process of taking the request object, Zend_Controller_Request_Abstract, extracting the controller name, action name, and optional parameters contained in it, and then instantiating a controller and calling an action of that controller. If no controller or action are found, it will use default values for them. Zend_Controller_Dispatcher_Standard specifies index for each of these defaults, but allows the developer to change them using the setDefaultController() and setDefaultAction() methods.

Dispatching happens in a loop in the front controller. Before dispatching occurs, the front controller routes the request to find user specified values for the controller, action, and optional parameters. It then enters a dispatch loop, dispatching the request.

At the beginning of each iteration, it sets a flag in the request object indicating that the action has been dispatched. If an action or pre/postDispatch plugin resets that flag, the dispatch loop will continue and attempt to dispatch the request again. By changing the controller and/or action in the request and resetting the dispatched flag, the developer may define a chain of requests to perform.

The action controller method that controlls such dispatching is _forward(); call this method from any of the pre/postDispatch() or action methods, providing a controller, action, and optionally any additional parameters you may wish to send to the new action:

public function myAction()
{
    // do some processing...
    // forward to another action, FooController::barAction(), in the current
    // module:
    $this->_forward('bar', 'foo', null, array('baz' => 'bogus'));
}

7.1.5. Response Object

The response object is the logical pair to the request object. Its purpose is to collate content and/or headers so that they may be returned en masse. Additionally, the front controller will pass any caught exceptions to the response object, allowing the developer to gracefully handle exceptions. This functionality may be overridden by setting Zend_Controller_Front::throwExceptions(true):

$front->throwExceptions(true);

To send the response output, including headers, use sendOutput().

$response->sendOutput();

Developers should make use of the response object in their action controllers. Instead of directly rendering output and sending headers, push them to the response object:

// Within an action controller action:
// Set a header
$this->getResponse()
    ->setHeader('Content-Type', 'text/html')
    ->appendBody($content);

By doing this, all headers get sent at once, just prior to displaying the content.

Should an exception occur in an application, check the response object's isException() flag, and retrieve the exception using getException(). Additionally, one may create custom response objects that redirect to error pages, log exception messages, do pretty formatting of exception messages (for development environments), etc.

You may retrieve the response object following the front controller dispatch(), or request the front controller to return the response object instead of rendering output.

// retrieve post-dispatch:
$front->dispatch();
$response = $front->getResponse();
if ($response->isException()) {
    // log, mail, etc...
}

// Or, have the front controller dispatch() process return it
$front->returnResponse(true);
$response = $front->dispatch();

// do some processing...

// finally, echo the response
$response->sendResponse();

By default, exception messages are not displayed. This behaviour may be overridden by calling renderExceptions(), or enabling the front controller to throwExceptions(), as shown above:

$response->renderExceptions(true);
$front->dispatch($request, $response);

// or:
$front->returnResponse(true);
$response = $front->dispatch();
$response->renderExceptions();
$response->sendOutput();

// or:
$front->throwExceptions(true);
$front->dispatch();