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
or extend the
classes Zend_Controller_Request_Abstract
,
Zend_Controller_Response_Abstract
, and
Zend_Controller_Action
.
Possible reasons for subclassing include:
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 or
GUI application. In the case of a console application,
a custom request object could process command line
arguments.
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
.
You wish to log application exceptions when caught and
redirect to a generic error page. Extending
Zend_Controller_Response_Http
, you could
modify __toString()
to check for registered
exceptions, log them, and then redirect to an error
page.
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.
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.
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_".
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.
Zend_Controller_Front implements a Front Controller. Additionally, it is a singleton class, meaning only one instance of it may be available at any given point.
To subclass it, at the very minimum you will need to override the
getInstance()
method:
class My_Controller_Front extends Zend_Controller_Front { public static function getInstance() { if (null === self::$_instance) { self::$_instance = new self(); } return self::$_instance; } }
Overriding the getInstance() method ensures that subsequent calls to
Zend_Controller_Front::getInstance()
will return an
instance of your new subclass instead of a Zend_Controller_Front
instance -- this is particularly useful for some of the alternate
routers and view helpers.
In addition to getInstance(), there are many other methods you may override:
/** * Resets all object properties of the singleton instance * * Primarily used for testing; could be used to chain front controllers. * * @return void */ public function resetInstance(); /** * Convenience feature, calls setControllerDirectory()->setRouter()->dispatch() * * In PHP 5.1.x, a call to a static method never populates $this -- so run() * may actually be called after setting up your front controller. * * @param string|array $controllerDirectory Path to Zend_Controller_Action * controller classes or array of such paths * @return void * @throws Zend_Controller_Exception if called from an object instance */ public static function run($controllerDirectory); /** * Add a controller directory to the controller directory stack * * If $args is presented and is a string, uses it for the array key mapping * to the directory specified. * * @param string $directory * @param mixed $args Optional argument; if string value, used as array key map * @return Zend_Controller_Front */ public function addControllerDirectory($directory, $args = null); /** * Set controller directory * * Stores controller directory to pass to dispatcher. May be an array of * directories or a string containing a single directory. * * @param string|array $directory Path to Zend_Controller_Action controller * classes or array of such paths * @return Zend_Controller_Front */ public function setControllerDirectory($directory); /** * Retrieve controller directory * * Retrieves stored controller directory * * @return string|array */ public function getControllerDirectory(); /** * Set the default controller (unformatted string) * * @param string $controller * @return Zend_Controller_Front */ public function setDefaultController($controller); /** * Retrieve the default controller (unformatted string) * * @return string */ public function getDefaultController(); /** * Set the default action (unformatted string) * * @param string $action * @return Zend_Controller_Front */ public function setDefaultAction($action); /** * Retrieve the default action (unformatted string) * * @return string */ public function getDefaultAction(); /** * Set request class/object * * Set the request object. The request holds the request environment. * * If a class name is provided, it will instantiate it * * @param string|Zend_Controller_Request_Abstract $request * @throws Zend_Controller_Exception if invalid request class * @return Zend_Controller_Front */ public function setRequest($request); /** * Return the request object. * * @return null|Zend_Controller_Request_Abstract */ public function getRequest(); /** * Set router class/object * * Set the router object. The router is responsible for mapping * the request to a controller and action. * * If a class name is provided, instantiates router with any parameters * registered via {@link setParam()} or {@link setParams()}. * * @param string|Zend_Controller_Router_Interface $router * @throws Zend_Controller_Exception if invalid router class * @return Zend_Controller_Front */ public function setRouter($router); /** * Return the router object. * * Instantiates a Zend_Controller_Router object if no router currently set. * * @return null|Zend_Controller_Router_Interface */ public function getRouter(); /** * Set the base URL used for requests * * Use to set the base URL segment of the REQUEST_URI to use when * determining PATH_INFO, etc. Examples: * - /admin * - /myapp * - /subdir/index.php * * Note that the URL should not include the full URI. Do not use: * - http://example.com/admin * - http://example.com/myapp * - http://example.com/subdir/index.php * * If a null value is passed, this can be used as well for autodiscovery (default). * * @param string $base * @return Zend_Controller_Front * @throws Zend_Controller_Exception for non-string $base */ public function setBaseUrl($base = null); /** * Retrieve the currently set base URL * * @return string */ public function getBaseUrl(); /** * Set the dispatcher object. The dispatcher is responsible for * taking a Zend_Controller_Request_Abstract object, instantiating the controller, and * calling the action method of the controller. * * @param Zend_Controller_Dispatcher_Interface $dispatcher * @return Zend_Controller_Front */ public function setDispatcher(Zend_Controller_Dispatcher_Interface $dispatcher); /** * Return the dispatcher object. * * @return Zend_Controller_DispatcherInteface */ public function getDispatcher(); /** * Set response class/object * * Set the response object. The response is a container for action * responses and headers. Usage is optional. * * If a class name is provided, instantiates a response object. * * @param string|Zend_Controller_Response_Abstract $response * @throws Zend_Controller_Exception if invalid response class * @return Zend_Controller_Front */ public function setResponse($response); /** * Return the response object. * * @return null|Zend_Controller_Response_Abstract */ public function getResponse(); /** * Add or modify a parameter to use when instantiating an action controller * * @param string $name * @param mixed $value * @return Zend_Controller_Front */ public function setParam($name, $value); /** * Set parameters to pass to action controller constructors * * @param array $params * @return Zend_Controller_Front */ public function setParams(array $params); /** * Retrieve a single parameter from the controller parameter stack * * @param string $name * @return mixed */ public function getParam($name); /** * Retrieve action controller instantiation parameters * * @return array */ public function getParams(); /** * Clear the controller parameter stack * * By default, clears all parameters. If a parameter name is given, clears * only that parameter; if an array of parameter names is provided, clears * each. * * @param null|string|array single key or array of keys for params to clear * @return Zend_Controller_Front */ public function clearParams($name = null); /** * Register a plugin. * * @param Zend_Controller_Plugin_Abstract $plugin * @return Zend_Controller_Front */ public function registerPlugin(Zend_Controller_Plugin_Abstract $plugin); /** * Unregister a plugin. * * @param Zend_Controller_Plugin_Abstract $plugin * @return Zend_Controller_Front */ public function unregisterPlugin(Zend_Controller_Plugin_Abstract $plugin); /** * Set whether exceptions encounted in the dispatch loop should be thrown * or caught and trapped in the response object * * Default behaviour is to trap them in the response object; call this * method to have them thrown. * * @param boolean $flag Defaults to true * @return boolean Returns current setting */ public function throwExceptions($flag = null); /** * Set whether {@link dispatch()} should return the response without first * rendering output. By default, output is rendered and dispatch() returns * nothing. * * @param boolean $flag * @return boolean Returns current setting */ public function returnResponse($flag = null); /** * Dispatch an HTTP request to a controller/action. * * @param Zend_Controller_Request_Abstract|null $request * @param Zend_Controller_Response_Abstract|null $response * @return void|Zend_Controller_Response_Abstract Returns response object if returnResponse() is true */ public function dispatch(Zend_Controller_Request_Abstract $request = null, Zend_Controller_Response_Abstract $response = null);
The purpose of the front controller is to setup the request environment, route the incoming request, and then dispatch any discovered actions. Finally, it aggregates any responses and returns them.
The main reasons to extend the front controller would be to change the logic for one of the accessor methods (for instance, to load a different default router or dispatcher, or to specify different logic for how controller directories are handled), or to change how routing and dispatching occur.
The abstract Zend_Controller_Request_Abstract
defines a
handful of methods:
/** * @return string */ public function getControllerName(); /** * @param string $value * @return self */ public function setControllerName($value); /** * @return string */ public function getActionName(); /** * @param string $value * @return self */ public function setActionName($value); /** * @return string */ public function getControllerKey(); /** * @param string $key * @return self */ public function setControllerKey($key); /** * @return string */ public function getActionKey(); /** * @param string $key * @return self */ public function setActionKey($key); /** * @param string $key * @return mixed */ public function getParam($key); /** * @param string $key * @param mixed $value * @return self */ public function setParam($key, $value); /** * @return array */ public function getParams(); /** * @param array $array * @return self */ public function setParams(array $array); /** * @param boolean $flag * @return self */ public function setDispatched($flag = true); /** * @return boolean */ public function isDispatched(); }
The request object is a container for the request environment. The controller chain really only needs to know how to set and retrieve the controller, action, optional parameters, and dispatched status. By default, the request will search its own parameters using the controller or action keys in order to determine the controller and action.
The interface Zend_Controller_Router_Interface
provides
a definition for only one method:
<?php /** * @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.
Zend_Controller_Front
will first call the router to
determine the first dispatchable action in the request. It then
enters a dispatch loop.
In the loop, it first sets the request object's dispatched flag and then dispatches the request (instantiate the controller, call its action). If the action method (or a pre/postDispatch plugin) resets the request object's dispatched flag, the front controller will do another iteration of the dispatch loop with whatever action is currently set in the request object. This 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_Request_Abstract $request * @return boolean */ public function isDispatchable(Zend_Controller_Request_Abstract $request); ?>
isDispatchable()
checks if a request 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_Request_Abstract $route * @return Zend_Controller_Request_Abstract */ public function dispatch(Zend_Controller_Request_Abstract $request); ?>
dispatch()
is where the work gets done. This method
must execute the action of the controller. It must return a
request object.
The Action Controller handles the various actions of an application. This abstract class provides the following methods:
/** * @param Zend_Controller_Request_Abstract $request Request object * @param Zend_Controller_Response_Abstract $response Response object * @param array $args Optional associative array of * configuration/environment settings */ public function __construct(Zend_Controller_Request_Abstract $request, Zend_Controller_Response_Abstract $response, array $args = array()); /** * @return void */ public function init(); /** * @return Zend_Controller_Request_Abstract */ public function getRequest(); /** * @param Zend_Controller_Request_Abstract $request * @return self */ public function setRequest(Zend_Controller_Request_Abstract $request); /** * @return Zend_Controller_Response_Abstract */ public function getResponse(); /** * @param Zend_Controller_Response_Abstract $response * @return self */ public function setResponse(Zend_Controller_Response_Abstract $response); /** * @return array */ public function getInvokeArgs(); /** * @return mixed */ public function getInvokeArg($name); public function preDispatch(); public function postDispatch(); /** * @param string $methodName * @param array $args */ public function __call($methodName, $args); /** * @param null|Zend_Controller_Request_Abstract $request Optional request * object to use * @param null|Zend_Controller_Response_Abstract $response Optional response * object to use * @return Zend_Controller_Response_Abstract */ public function run(Zend_Controller_Request_Abstract $request = null, Zend_Controller_Response_Abstract $response = null);
The constructor registers the request and response objects with the
object, as well as an array of any additional configuration
arguments. This last array consists of parameters registered with
the Front Controller's setParam()
or
setParams()
methods. Once done, the constructor passes
handling to init()
.
While you may override the constructor, we suggest putting any
initialization handling into init()
to ensure the
request and response objects are properly registered.
Any configuration arguments passed to the constructor are later
accessible using getInvokeArg()
and
getInvokeArgs()
. The recommendation is to use such
invocation arguments to pass in objects such as view,
authentication/authorization, or registry objects. For example:
$front = Zend_Controller_Front::getInstance(); $front->setParam('view', new Zend_View()) ->setControllerDirectory($config->controller->directory); $response = $front->dispatch(); // In a sample action controller: class FooController extends Zend_Controller_Action { protected $_view = null; public function init() { $this->_view = $this->getInvokeArg('view'); } }
When an action is dispatched, processing may be performed before and
after the action using the preDispatch()
and
postDispatch()
methods, respectively. By default, they
are empty and do nothing.
The __call()
method will handle any unregistered
actions in the class. By default, it throws an exception if the
action is not defined. This should only ever occur if the default
action method is not defined.
The default naming convention for action methods is lowercaseAction,
where 'lowercase' specifies the name of the action, and 'Action'
specifies that the method is an action method. Thus,
http://framework.zend.com/foo/bar
will call
FooController::barAction()
.
Action controllers may also be used as Page Controllers. Most typical usage would be as follows:
$controller = new FooController( new Zend_Controller_Request_Abstract(), new Zend_Controller_Response_Abstract() ); $controller->run();
Use Front-/Action Controller | |
---|---|
We recommend using the Front Controller/Action Controller combination instead of the Page Controller paradigm to encourage writing applications that will inter-operate. |
The Response Object collects content and headers from the various actions called and returns them to the client. It has the following methods:
/** * @param string $name Header name * @param string $value Header value * @param boolean $replace Whether or not to replace headers with the same * name already registered with the object * @return self */ public function setHeader($name, $value, $replace = false); /** * @return array */ public function getHeaders(); /** * @return void */ public function clearHeaders(); /** * Sends all headers * @return void */ public function sendHeaders(); /** * @param string $content * @return self */ public function setBody($content); /** * @param string $content * @return self */ public function appendBody($content); /** * @return string */ public function getBody(); /** * echoes body content * @return void */ public function outputBody(); /** * @param Exception $e * @return self */ public function setException(Exception $e); /** * @return null|Exception */ public function getException(); /** * @return boolean */ public function isException(); /** * @param boolean $flag * @return boolean */ public function renderExceptions($flag = null); /** * @return string */ public function __toString();
setBody()
will replace all body content; we encourage using
appendBody()
instead. __toString()
should
render any content and send all headers.
The response object is also where action controller exceptions are
finally caught and registered (unless
Zend_Controller_Front::throwExceptions()
has been
enabled. isException()
should return a boolean
indicating whether or not this has occurred.
renderExceptions()
should be used to indicate whether
__toString()
will render exception output if an
exception was trapped.