4.3. Subclassing

4.3.1. Introduction

The Zend_Controller system was built with extensibility in mind, either by subclassing the existing classes or writing new classes that implement the interfaces Zend_Controller_Router_Interface and Zend_Controller_Dispatcher_Interface.

Possible reasons for implementing a new router or dispatcher might be:

  • The existing URI routing system is not suitable in some way, such as integrating into an existing website that uses its own conventions for routing that do not mesh with the routing mechanism provided with the Zend Framework.

  • You need to implement routing for something completely different. The Zend_Controller_Router class only deals with URIs. It's possible and likely that you might want to use the MVC pattern for developing another type of program, such as a console application. In the case of a console application, a custom router could process command line arguments to determine the route.

  • The mechanism provided by Zend_Controller_Dispatcher is not suitable. The default configuration assumes a convention that controllers are classes and actions are methods of those classes. However, there are many other strategies for doing this. One example would be where controllers are directories and actions are files within those directories.

  • You wish to provide additional capabilities that will be inherited by all of your controllers. For example, Zend_Controller_Action does not integrate with Zend_View by default. However, you could extend your own controller to do this, and using it would not require modifying the supplied Zend_Controller_Router or Zend_Controller_Dispatcher.

Please be careful when overriding significant parts of the system, particularly the dispatcher. One of the advantages of Zend_Controller is that it establishes common conventions for building applications. If too much of this default behavior is changed, some of these advantages are lost. However, there are many different needs and one solution can't fit them all, so the freedom is provided if needed.

4.3.2. Conventions

When subclassing any of the Zend_Controller classes, you are strongly encouraged to follow these conventions for naming or storing files. Doing so will ensure that another programmer who is familiar with the Zend Framework will be able to understand your project easily.

4.3.2.1. Prefix

Classes included with the Zend Framework follow a convention where every class is prefixed by "Zend_". This is the prefix. We recommend that you name all of your classes in the same way, e.g. if your company name is Widget, Inc., the prefix might be "Widget_".

4.3.2.2. Directory Layout

The Zend_Controller classes are stored in the library directory as follows:

/library
  /Zend
    /Controller
      Action.php
      Dispatcher.php
      Router.php

When subclassing Zend_Controller classes, it is recommended that the new classes be stored in an identical structure under your prefix. This will make them easy to find for someone during that learning process of reviewing the code for your project.

For example, a project from Widget, Inc. which implements only a custom router might look like this:

/library
  /Zend
  /Widget
    /Controller
      Router.php
      README.txt

Notice in this example that the Widget/Controller/ directory mirrors the Zend/Controller/ directory wherever possible. In this case, it provides the class Widget_Controller_Router, which would be either a subclass or replacement for Zend_Controller_Router implementing Zend_Controller_Router_Interface.

Also, notice in the example above that a README.txt file has been placed in Widget/Controller/. Zend strongly encourages you to document your projects through supplying separate tests and documentation for customers. However, we encourage you to also place a simple README.txt file right in the directory to briefly explain your changes and how they work.

4.3.3. Router Interface

The interface Zend_Controller_Router_Interface provides a definition for only one method:

<?php
				
  /**				
   * @param  Zend_Controller_Dispatcher_Interface
   * @throws Zend_Controller_Router_Exception
   * @return Zend_Controller_Dispatcher_Token|boolean
   */
  public function route(Zend_Controller_Dispatcher_Interface $dispatcher);

?>

Routing only occurs once: when the request is first received into the system. The purpose of the router is to generate a Zend_Controller_Dispatch_Token that specifies a controller and action of that controller. This is then passed to the dispatcher. If it is not possible to map a route to a dispatch token (nonsenical route) then a boolean, FALSE, should be returned.

Some routers may process dynamic elements and need a way to determine if the dispatch token generated is actually dispatchable before returning it. For this reason, the router receives the object handle of the dispatcher as the sole argument to its route() method. The dispatcher provides a method, isDispatchable(), for this testing.

4.3.4. Dispatcher Interface

Zend_Controller_Front will first call the router to receive the first dispatch token, which it will pass to the dispatcher. The dispatcher will dispatch the action (instantiate the controller, call its action) and then return with either a boolean, FALSE, or another dispatch token.

Zend_Controller_Front repeatedly calls the dispatcher until a dispatch token is not returned from it. This is known as the dispatch loop. It allows for actions to be processed sequentially until all work has been done.

The interface Zend_Controller_Dispatcher_Interface provides definitions for two methods:

<?php
				
/**
 * @param  Zend_Controller_Dispatcher_Token $route
 * @return boolean
 */
public function isDispatchable(Zend_Controller_Dispatcher_Token $route);

?>

isDispatchable() checks if a dispatch token is dispatchable. If it is, it returns TRUE. Otherwise, it returns FALSE. The definition of what is dispatchable is left to the class implementing the interface. In the case of the default implementation, Zend_Controller_Dispatcher, it means that the controller file exists, the class exists within the file, and the action method exists within the class.

<?php
			
/**
 * @param  Zend_Controller_Dispatcher_Token $route
 * @return Zend_Controller_Dispatcher_Token|boolean
 */
public function dispatch(Zend_Controller_Dispatcher_Token $route);

?>

dispatch() is where the work gets done. This method must execute the action of the controller. It must return either a dispatch token or a boolean, FALSE, to indicate that there is no more work to do.