Zend Framework: Part 2 – Zend_Controller

Zend_Controller

Zend_Controller is the heart of Zend Framework’s MVC system. Zend_Controller_Front implements a Front Controller pattern, in which all requests are intercepted by the front controller (Zend_Controller_Front) and dispatched to individual Action Controllers (Zend_Controller_Action) based on the URL requested. Its purpose is to initialize the request environment, route the incoming request, and then dispatch any discovered actions; it aggregates any responses and returns them when the process is complete. Zend_Controller_Front also implements the Singleton pattern, meaning only a single instance of it may be available at any given time. Let’s have a look at the Zend_Controller diagram which will show us the basics:

Zend_Controller basics
Zend_Controller basics

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.

The Router

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.

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

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.

The Dispatcher

Dispatching is the process of taking the request object, Zend_Controller_Request_Abstract, extracting the module name, controller name, action name, and optional parameters contained in it, and then instantiating a controller and calling an action of that controller.  If any of the module, controller, or action are not found, it will use default values for them. Zend_Controller_Dispatcher_Standard specifies index for each of the controller and action defaults and default for the module default value.

Dispatching happens in a loop in the front controller. 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 new request.

Plugins

Zend_Controller_Front registers a plugin broker with itself, allowing various events it triggers to be observed by plugins. In most cases, this gives the developer the opportunity to tailor the dispatch process to the site without the need to extend the front controller to add functionality. The plugin broker ensures that event methods are called on each plugin registered with the front controller. The event methods are defined in the abstract class Zend_Controller_Plugin_Abstract, from which user plugin classes inherit:

  • routeStartup() is called before Zend_Controller_Front calls on the router to evaluate the request against the registered routes
  • routeShutdown() is called after the router finishes routing the request
  • dispatchLoopStartup() is called before Zend_Controller_Front enters its dispatch loop
  • preDispatch() is called before an action is dispatched by the dispatcher. This callback allows for proxy or filter behavior. By altering the request and resetting its dispatched flag, the current action may be skipped and/or replaced
  • postDispatch() is called after an action is dispatched by the dispatcher. This callback allows for proxy or filter behavior. By altering the request and resetting its dispatched flag, a new action may be specified for dispatching
  • dispatchLoopShutdown() is called after Zend_Controller_Front exits its dispatch loop

MVC Exceptions

All exceptions bubble up to the Front Controller, allowing the developer to handle them in a single location. By default Zend_Controller_Front catches all exceptions and registers them with the response object; in turn, by default, the response object does not display exception messages.

Action Controllers

Before discussing action controllers, let’s have a look at the work that has already been done by the router and dispatcher. By default, the first segment of a URL path maps to a controller, and the second to an action. For example, given the URL www.test.com/login/loginuser, the path is /login/loginuser, which will map to the controller login and the action loginuser. If no action is provided, the action index is assumed, and if no controller is provided, the controller index is assumed. Zend_Controller’s dispatcher then takes the controller value and maps it to a class. By default, it Title-cases the controller name and appends the word Controller. Thus, in our example, the controller login is mapped to the class LoginController. Similarly, the action value is mapped to a method of the controller class. By default, the value is lowercased, and the word Action is appended. Thus, in our example, the action components becomes loginuserAction, and the final method called is LoginController::loginuserAction().

Because Zend Framework’s MVC implementation makes use of the Front Controller pattern, you must therefore rewrite all incoming requests (except those for static resources, which your application need not handle) to a single script that will initialize the FrontController and route the request. An example if you want to use mod_rewrite for Apache:

# public/.htaccess

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

This set of rewrite rules specify that if the file exists under the document root directory, it should simply be served as a static resource. Otherwise, the request is for dynamic content and should be rewritten to our index.php script. Since all requests for non-static content will be rewritten to it, the index.php script serves as the entry point to our application.

Zend_Controller_Action is an abstract class you may use for implementing Action Controllers for use with the Front Controller when building a website based on the MVC pattern.  Let’s have a look at an example:

class LoginController extends Zend_Controller_Action {
  public function init() {
    // Called after the __construct ()
    // For example: connect to an database at instantiation
  }
 
  public function preDispatch(Zend_Controller_Request_Abstract $request) {
    //  For example: verifying authentication and ACLs prior to running an action (by calling _forward() the action will be skipped)
  }    
 
  public function postDispatch(Zend_Controller_Request_Abstract $request) {
    //  For example: placing generated content in a sitewide template
  }
}

Zend_Controller_Action specifies two methods that may be called to bookend a requested action, preDispatch() and postDispatch(). These can be useful in a variety of ways: verifying authentication and ACLs prior to running an action, for instance, or placing generated content in a sitewide template.

The ViewRenderer

By default, the front controller enables the ViewRenderer action helper. This helper takes care of injecting the view object into the controller, as well as automatically rendering views. You may disable it within your action controller. The primary reasons to disable the ViewRenderer are if you simply do not need a view object or if you are not rendering via view scripts (for instance, when using an action controller to serve web service protocols such as SOAP). In most cases, you will never need to globally disable the ViewRenderer, only selectively within individual controllers or actions. The ViewRenderer helper is designed to satisfy the following goals:

  • Eliminate the need to instantiate view objects within controllers; view objects will be automatically registered with the controller
  • Automatically set view script, helper, and filter paths based on the current module, and automatically associate the current module name as a class prefix for helper and filter classes
  • Create a globally available view object for all dispatched controllers and actions
  • Allow the developer to set default view rendering options for all controllers
  • Add the ability to automatically render a view script with no intervention
  • Allow the developer to create her own specifications for the view base path and for view script paths

The first time an action controller is instantiated, it will trigger the ViewRenderer to instantiate a view object. Each time a controller is instantiated, the ViewRenderer’s init() method is called, which will cause it to set the view property of the action controller, and call addScriptPath() with a path relative to the current module; this will be called with a class prefix named after the current module, effectively namespacing all helper and filter classes you define for the module. Each time postDispatch() is called, it will call render() for the current action. Let’s have a look at an example:

// A controller class, login module:
class LoginController extends Zend_Controller_Action {
  // Render login/index.phtml by default; no action required
  public function indexAction() {
  }
 
  // Render login/logout.phtml with variable 'name' set to 'testuser'.
  // Since view object defined at preDispatch(), it's already available.
  public function logoutAction() {
    $this->view->name = 'testuser';
  }
}

The ViewRenderer does some path normalization to make view script lookups easier. The default rules are as follows:

  • :module: MixedCase and camelCasedWords are separated by dashes, and the entire string cast to lowercase. E.g.: “SiteLoginTest” becomes “site-login-test”
  • controller: MixedCase and camelCasedWords are separated by dashes; underscores are converted to directory separators, and the entire string cast to lower case. Examples: “SiteLogin” becomes site-login”; “SiteLogin_Test” becomes “site-login/test”
  • :action: MixedCase and camelCasedWords are separated by dashes; non-alphanumeric characters are translated to dashes, and the entire string cast to lower case. Examples: “siteLogin” becomes “site-login”; “site-loginTest” becomes “site-login-test”

The Response Object

The purpose of the response object 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.By default, the front controller calls sendResponse() when it has finished dispatching the request.


Leave a Reply