Zend - Framework 1.11 - Using Zend_Controller

Let's explain, with this tutorial, how this Zend_Controller works.

I added some examples in addition of the official documentation, even if most of my text is taking from the Zend documentation.

Zend_Controller_Front is the heart of this MVC framework.
It intercepts all requests and dispatches them to all adequate controllers.
Each controller has one or several action methods and each action has a view script.
It means that if we have a MenuController with a method named firstAction(), we will have the template /view/scripts/menu/first.phtml to display content of the firstAction() method.

This mechanism is possible thanks to the ViewRenderer action helper.

The Zend_Controller_Request_Abstract helps the Zend_Controller_Front to retrieve correct controllers, actions and parameters. It knows if the action has been dispatched by the Zend_Controller_Dispatcher.
Thus the entire HTTP request environment can be saved and used by the Zend_Controller_Request_Http.

Then the Zend_Controller_Router_Interface is used to define routers by examining the request environment.
After the Zend_Controller_Request_Abstract set correctly the controller, action and parameters, the Zend_Controller_Dispatcher_Standard can processes them.
This process intervenes only one time before the dispatching of the first controller.
The default router, Zend_Controller_Router_Rewrite takes elements found in the Zend_Controller_Request_Http, with the URL,  to create controllers, actions and parameters.
Example: http://www.google.com/hello/world/welcome/badprog/ will transform this URL into the hello controller, world action, and parameters welcome key and badprog value.

We use next the Zend_Controller_Dispatcher_Interface to define dispatchers. Dispatchers will find the controller, actions and parameters from the Zend_Controller_Request_Abstract. Then they will try to map them to a controller file and an action method. The default controller and action are disaptched if there is no mapping.

The dispatching process occurs in a loop. It means that until there is a request object dispatched (status set to TRUE), the loop will continue.

Each controller must extend Zend_Controller_Action.

Finally the Zend_Controller_Response_Abstract is used to retrieve and send responses from the action controllers. Headers and body content are collected. The default response class is Zend_Controller_Response_Http.

The workflow of Zend_Controller is relatively simple.

  1. Zend_Controller_Front received a request
  2. In turn it calls Zend_Controller_Router_Rewrite to determine which controller (and action in that controller) to dispatch
  3. Zend_Controller_Router_Rewrite decomposes the URI in order to set the controller and action names in the request
  4. Zend_Controller_Front enters a dispatch loop
  5. It calls Zend_Controller_Dispatcher_Standard, passing it the request, to dispatch to the controller and action specified in the request (or use defaults)
  6. After the controller has finished, control returns to Zend_Controller_Front

The controller can indicate that another controller should be dispatched. So it resets the dispatched status of the request and the loop continues.
Another dispatch is performed otherwise the process ends.

Calling the Zend_Controller_Front can be done by writing it in the index.php file, as follows:

/** Zend_Application */
require_once 'Zend/Application.php';

/** Zend_Loader_Autoloader */
require_once 'Zend/Loader/Autoloader.php';
Zend_Loader_Autoloader::getInstance();

/** Zend_Controller_Front */
$front = Zend_Controller_Front::getInstance();

I included the Zend_Loader_Autoloader instance because it is really helpful!
Note the Zend_Application that is already in your index.php if you created the project with the command line. So do not include it a second time.

Of course this call of the Zend_Controller_Front is  unique because this is a Singleton pattern.
That's why we use its getInstance() method.

You can use several methods on the Zend_Controller_Front. Take a look a the official Zend Framework API.

You can also extend the Zend_Controller_Front. Let's see it:

class My_Controller_Front extends Zend_Controller_Front
{
    public static function getInstance()
    {
        if (null === self::$_instance) {
            self::$_instance = new self();
        }
 
        return self::$_instance;
    }
}




We test if the self::$_instance is already defined. If not we do and return this new instance.

The Request Object

The request object is a value object (VO) intercepted by the Zend_Controller_Front and transmit to the appropriate:

  • Module accessed by the getModuleName() method
  • Controller accessed by the getControllerName() method
  • Action accessed by the getActionName() method
  • Parameters accessed by the getParams() or getParam() method

This request object is instantiated before routing occurs and at the beginning of the dispatch process.

HTTP Requests

Superglobals can be accessed by certain methods, such as getPost('user') on the request object for the value $_POST['user'].
The same for getQuery() for the $_GET elements or getHeader() for the header requests.

It is possible to specify which parameter source is allowed, _GET or _POST.
For example, simply use the setParamSources(array('_POST')) method for the _POST param.
Be aware, by default, both are allowed.

Note that if you want to use the Apache's 404 handler, the URI is contained in the superglobal $_SERVER['REDIRECT_URL'] not $_SERVER['REDIRECT_URI'].
The Zend_Controller_Request_Apache404, that extends Zend_Controller_Request_Http, is appropriate instead of the HTTP class for the request object, if you have invalid routing:

$request = new Zend_Controller_Request_Apache404();
$front->setRequest($request);

You can specify the base URL of your project. For example, if the path of your project is http://localhost/myproject/badprog/

The code might be:

$router     = new Zend_Controller_Router_Rewrite();
$controller = Zend_Controller_Front::getInstance();
$controller->setControllerDirectory('./application/controllers')
           ->setRouter($router)
           ->setBaseUrl('/myproject/badprog');
$response   = $controller->dispatch();

RESTful requests

RESTful MVC architecture can be simplified with methods already implemented, such as:

  • isDelete()
  • isGet()
  • isHead()
  • isOptions
  • isPost()
  • isPut()

AJAX requests

The main method for detecting AJAX request is the isXmlHttpRequest(). If the value XMLHttpRequest is found in the header sent, it returns TRUE.

The request object container

All request objects must extend the Zend_Controller_Request_Abstract class.
The request object is a container for the request environment. That's why the Zend_Controller_Front needs it to know the modules, controllers, actions and parameters to capture and dispatch.

The Router

The standard router is the Zend_Controller_Router_Route. It packages the module, controller, action and parameter according to the URL passed.
These data are packaged into a Zend_Controller_Request_Http object which is then processed by the Zend_Controller_Dispatcher_Standard.
This routing occurs only once before the first controller dispatching.

To use this Zend_Controller_Router_Rewrite, the Apache mod_rewrite rule must be activated, for example in the .htaccess we can have:

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

Creating a router is easy. We have first of all to instantiate it:

$controllerRouterRewrite = new Zend_Controller_Router_Rewrite();
$controllerRouterRewrite->addRoute(
    'user',
    new Zend_Controller_Router_Route('user/:username',
                                     array('controller' => 'user',
                                           'action' => 'info'))
);

In this case, the first parameter is the name of the route, the second is the route itself.

All routes provided are tested with the request URI. If a positive match is found, variable values are returned from the route instance and injected into the Zend_Controller_Request object.
On a negative matching, the next route in the chain is tested.

The getCurrentRouteName() and getCurrentRoute() method can help us to know the route current name and the current route object.

The result is something like that:

Module + controller + action + params:
http://example/blog/archive/list/sort/alpha/date/desc
    module     == blog
    controller == archive
    action     == list
    sort       == alpha
    date       == desc

We have to note that in the route definition, the first parameter is static and the second is dynamic.
We can see it with the colon before the second, take a look at this example:

$route = new Zend_Controller_Router_Route(
    'author/:username',
    array(
        'controller' => 'profile',
        'action'     => 'userinfo'
    )
);
$router->addRoute('user', $route);

So in this example, we can see the author as a static part and the :username as a dynamic one.

If with our URL (http://www.example.com/author/martel), we have a match with our example, all variables will be injected to the Zend_Controller_Request object.
And now if we create a ProfileController class, all these variables will be accessible in our ProfileController.
Variables returned may be represented as an array:

$values = array(
    'username'   => 'martel',
    'controller' => 'profile',
    'action'     => 'userinfo'
);

Later on, the Zend_Controller_Dispatcher_Standard should invoke the userInfoAction() method of our ProfileController:

// ProfileController
public function userinfoAction()
{
    $request = $this->getRequest();
    // Both below are equal
    $username = $request->getParam('username');
    $username = $this->_getParam('username');
}

Variable defaults

The second parameter of the Zend_Controller_Router_Route can have a default value:

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

You can also add a default controller and action with this variable:

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

The third parameter can be omit. It concerns regular expression (REGEX), for example the code below will match with a numeric variable, otherwise control will be passed to the next route in the chain.

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

Using the fourth parameter as a translated segment

The first parameter can be used in conjunction with Zend_Translate and Zend_Locale.

Static Controller

Routes can be set in stone.
We want that something appears when we reach the login form. For example, by reaching the login form, we want to access the AuthController and its loginAction() method:

$route = new Zend_Controller_Router_Route_Static(
    'login',
    array('controller' => 'auth', 'action' => 'login')
);
$router->addRoute('login', $route);

The above route will match a URL such as http://www.example.com/login and dispatch to AuthController::loginAction().
Note that when you are using the static routes, you must specify the default values of the controller, action and module if it is not the default.

Zend_Controller_Router_Route_Regex

This route is helpful if you want a most complex system of retrieving information from the URI.

Zend_Controller_Router_Route_Hostname

This route is helpful if you want to use a subdomain system, such as http://author.country.example.com/ instead of http://wwwm.example.com/author/country

Zend_Controller_Router_Route_Chain

This route allow to chain multiple routes together, for example:

// Create two routes
$hostnameRoute = new Zend_Controller_Router_Route_Hostname(...);
$pathRoute     = new Zend_Controller_Router_Route(...);
 
// First way, chain them via the chain route
$chainedRoute = new Zend_Controller_Router_Route_Chain();
$chainedRoute->chain($hostnameRoute)
             ->chain($pathRoute);
 
// Second way, chain them directly
$chainedRoute = $hostnameRoute->chain($pathRoute);

When chaining routes together, the default separator is the slash. It can be changed:

// Create two routes
$firstRoute  = new Zend_Controller_Router_Route('foo');
$secondRoute = new Zend_Controller_Router_Route('bar');
 
// Chain them together with a different separator
$chainedRoute = $firstRoute->chain($secondRoute, '-');
 
// Assemble the route: "foo-bar"
echo $chainedRoute->assemble();

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.

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 or postDispatch plugin resets that flag, the dispatch loop will continue and attempt to dispatch the new request. 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 controls such dispatching is _forward(); call this method from any of the preDispatch(), postDispatch() or action methods, providing an action, controller, module, and optionally any additional parameters you may wish to send to the new action:

// IndexController.php

    public function fooAction()
    {
        $this->_forward('bar', null, null);
    }

    public function barAction()
    {
        $this->_forward('baz', null, null, null);
    }
    
    public function bazAction()
    {
        $this->_forward('baz', 'menu', null, array('baz' => 'bogus'));
       
    }

Note the "null, null, null" parameters in the barAction().
This can be omit of course because there is no other parameter after them.
However in the bazAction, we need this "null" to say to the _forward method to use the default value, in this case "index".

Then in MenuController:

// MenuController

public function bazAction()
    {
        $this->view->gogogo = $this->_getParam('baz');
    }

Finally in the view script:

// view/scripts/menu/baz.phtml

<?php
echo $this->gogogo;

It will display "bogus".

Zend_Controller_Action

All controllers have to extend the Zend_Controller_Action.

Object initialization

Override the Zend_Controller_Action::__construct() is not recommended because it does some important tasks.
But it is always possible.
Be sure to call parent::__construct($request, $response, $invokeArgs).
The more appropriate way to customize instantiation is to use the init() method, which is called as the last task of __construct(). For example, if you want to connect to a database at instantiation:

class FooController extends Zend_Controller_Action
{
    public function init()
    {
        $this->db = Zend_Db::factory('Pdo_Mysql', array(
            'host'     => 'myhost',
            'username' => 'user',
            'password' => 'XXXXXXX',
            'dbname'   => 'website'
        ));
    }
}

PreDispatch and PostDispach

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 ACL's prior to running an action (by calling _forward() in preDispatch(), the action will be skipped), for instance, or placing generated content in a sitewide template ( postDispatch()).

Note: Usage of init() vs. preDispatch()
We introduced the init() method, and in this section, the preDispatch() method. What is the difference between them, and what actions would you take in each?
The init() method is primarily intended for extending the constructor. Typically, your constructor should simply set object state, and not perform much logic. This might include initializing resources used in the controller (such as models, configuration objects, etc.), or assigning values retrieved from the front controller, bootstrap, or a registry.
The preDispatch() method can also be used to set object or environmental (e.g., view, action helper, etc.) state, but its primary purpose is to make decisions about whether or not the requested action should be dispatched. If not, you should then _forward() to another action, or throw an exception.
Note: _forward() actually will not work correctly when executed from init(), which is a formalization of the intentions of the two methods.

Let's take an example:

// MenuController

class MenuController extends Zend_Controller_Action
{
    public function init()
    {
        echo "init()";
    }

    public function preDispatch()
    {
        echo "preDispatch()</br>";
    }
    
    public function postDispatch()
    {
        echo "postDispatch()</br>";
    }

    public function indexAction()
    {
        echo "indexAction()</br>";
    }
}

Open the URI http://localhost/menu.
Normally you will see displayed "init()" at the top of your webpage, even before the <!DOCTYPE html> tag.
In the content of the webpage you will see displayed:

preDispatch()
indexAction()
postDispatch()


Helpers

There is a lots of helpers in Zend Framework for the Zend_Controller.

I would like to talk particularly of the ViewRenderer.

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.

Note: If you perform a _forward(), redirect(), or render() manually, autorendering will not occur, as by performing any of these actions you are telling the ViewRenderer that you are determining your own output.
Note: The ViewRenderer is enabled by default. You may disable it via the front controller noViewRenderer param ($front->setParam('noViewRenderer', true);) or removing the helper from the helper broker stack ( Zend_Controller_Action_HelperBroker::removeHelper('viewRenderer')).
If you wish to modify settings of the ViewRenderer prior to dispatching the front controller, you may do so in one of two ways:
Instantiate and register your own ViewRenderer object and pass it to the helper broker:

$viewRenderer = new Zend_Controller_Action_Helper_ViewRenderer();
$viewRenderer->setView($view)
             ->setViewSuffix('php');
Zend_Controller_Action_HelperBroker::addHelper($viewRenderer);
Initialize and/or retrieve a ViewRenderer object on demand via the helper broker:

$viewRenderer =
    Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer');
$viewRenderer->setView($view)
             ->setViewSuffix('php');

At its most basic usage, you simply instantiate the ViewRenderer and pass it to the action helper broker. The easiest way to instantiate it and register in one go is to use the helper broker's getStaticHelper() method:

Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer');
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.

As an example, consider the following class:

// A controller class, foo module:
class Foo_BarController extends Zend_Controller_Action
{
    // Render bar/index.phtml by default; no action required
    public function indexAction()
    {
    }
 
    // Render bar/populate.phtml with variable 'foo' set to 'bar'.
    // Since view object defined at preDispatch(), it's already available.
    public function populateAction()
    {
        $this->view->foo = 'bar';
    }
}
 
...
 
// in one of your view scripts:
$this->foo(); // call Foo_View_Helper_Foo::foo()

The ViewRenderer also defines a number of accessors to allow setting and retrieving view options:

setView($view) allows you to set the view object for the ViewRenderer. It gets set as the public class property $view.

setNeverRender($flag = true) can be used to disable or enable autorendering globally, i.e., for all controllers. If set to TRUE, postDispatch() will not automatically call render() in the current controller. getNeverRender() retrieves the current value.

setNoRender($flag = true) can be used to disable or enable autorendering. If set to TRUE, postDispatch() will not automatically call render() in the current controller. This setting is reset each time preDispatch() is called (i.e., you need to set this flag for each controller for which you don't want autorenderering to occur). getNoRender() retrieves the current value.

setNoController($flag = true) can be used to tell render() not to look for the action script in a subdirectory named after the controller (which is the default behaviour). getNoController() retrieves the current value.

setNeverController($flag = true) is analogous to setNoController(), but works on a global level -- i.e., it will not be reset for each dispatched action. getNeverController() retrieves the current value.

setScriptAction($name) can be used to specify the action script to render. $name should be the name of the script minus the file suffix (and without the controller subdirectory, unless noController has been turned on). If not specified, it looks for a view script named after the action in the request object. getScriptAction() retrieves the current value.

setResponseSegment($name) can be used to specify which response object named segment to render into. If not specified, it renders into the default segment. getResponseSegment() retrieves the current value.

initView($path, $prefix, $options) may be called to specify the base view path, class prefix for helper and filter scripts, and ViewRenderer options. You may pass any of the following flags: neverRender, noRender, noController, scriptAction, and responseSegment.

setRender($action = null, $name = null, $noController = false) allows you to set any of scriptAction, responseSegment, and noController in one pass. direct() is an alias to this method, allowing you to call this method easily from your controller:

// Render 'foo' instead of current action script
$this->_helper->viewRenderer('foo');

// render form.phtml to the 'html' response segment, without using a
// controller view script subdirectory:
$this->_helper->viewRenderer('form', 'html', true);
Note: setRender() and direct() don't actually render the view script, but instead set hints that postDispatch() and render() will use to render the view.
The constructor allows you to optionally pass the view object and ViewRenderer options; it accepts the same flags as initView():

$view    = new Zend_View(array('encoding' => 'UTF-8'));
$options = array('noController' => true, 'neverRender' => true);
$viewRenderer =
    new Zend_Controller_Action_Helper_ViewRenderer($view, $options);

There are several additional methods for customizing path specifications used for determining the view base path to add to the view object, and the view script path to use when autodetermining the view script to render. These methods each take one or more of the following placeholders:

:moduleDir refers to the current module's base directory (by convention, the parent directory of the module's controller directory).

:module refers to the current module name.

:controller refers to the current controller name.

:action refers to the current action name.

:suffix refers to the view script suffix (which may be set via setViewSuffix()).

The methods for controlling path specifications are:

setViewBasePathSpec($spec) allows you to change the path specification used to determine the base path to add to the view object. The default specification is :moduleDir/views. You may retrieve the current specification at any time using getViewBasePathSpec().

setViewScriptPathSpec($spec) allows you to change the path specification used to determine the path to an individual view script (minus the base view script path). The default specification is :controller/:action.:suffix. You may retrieve the current specification at any time using getViewScriptPathSpec().

setViewScriptPathNoControllerSpec($spec) allows you to change the path specification used to determine the path to an individual view script when noController is in effect (minus the base view script path). The default specification is :action.:suffix. You may retrieve the current specification at any time using getViewScriptPathNoControllerSpec().

For fine-grained control over path specifications, you may use Zend_Filter_Inflector. Under the hood, the ViewRenderer uses an inflector to perform path mappings already. To interact with the inflector -- either to set your own for use, or to modify the default inflector, the following methods may be used:

getInflector() will retrieve the inflector. If none exists yet in the ViewRenderer, it creates one using the default rules.

By default, it uses static rule references for the suffix and module directory, as well as a static target; this allows various ViewRenderer properties the ability to dynamically modify the inflector.

setInflector($inflector, $reference) allows you to set a custom inflector for use with the ViewRenderer. If $reference is TRUE, it will set the suffix and module directory as static references to ViewRenderer properties, as well as the target.

Note: Default Lookup Conventions
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.: "FooBarBaz" becomes "foo-bar-baz".

Internally, the inflector uses the filters Zend_Filter_Word_CamelCaseToDash and Zend_Filter_StringToLower.

:controller: MixedCase and camelCasedWords are separated by dashes; underscores are converted to directory separators, and the entire string cast to lower case. Examples: "FooBar" becomes "foo-bar"; "FooBar_Admin" becomes "foo-bar/admin".

Internally, the inflector uses the filters Zend_Filter_Word_CamelCaseToDash, Zend_Filter_Word_UnderscoreToSeparator, and Zend_Filter_StringToLower.

:action: MixedCase and camelCasedWords are separated by dashes; non-alphanumeric characters are translated to dashes, and the entire string cast to lower case. Examples: "fooBar" becomes "foo-bar"; "foo-barBaz" becomes "foo-bar-baz".

Internally, the inflector uses the filters Zend_Filter_Word_CamelCaseToDash, Zend_Filter_PregReplace, and Zend_Filter_StringToLower.

The final items in the ViewRenderer API are the methods for actually determining view script paths and rendering views. These include:

renderScript($script, $name) allows you to render a script with a path you specify, optionally to a named path segment. When using this method, the ViewRenderer does no autodetermination of the script name, but instead directly passes the $script argument directly to the view object's render() method.

Note: Once the view has been rendered to the response object, it sets the noRender to prevent accidentally rendering the same view script multiple times.
Note: By default, Zend_Controller_Action::renderScript() proxies to the ViewRenderer's renderScript() method.
getViewScript($action, $vars) creates the path to a view script based on the action passed and/or any variables passed in $vars. Keys for this array may include any of the path specification keys ('moduleDir', 'module', 'controller', 'action', and 'suffix'). Any variables passed will be used; otherwise, values based on the current request will be utlized.

getViewScript() will use either the viewScriptPathSpec or viewScriptPathNoControllerSpec based on the setting of the noController flag.

Word delimiters occurring in module, controller, or action names will be replaced with dashes ('-'). Thus, if you have the controller name 'foo.bar' and the action 'baz:bat', using the default path specification will result in a view script path of 'foo-bar/baz-bat.phtml'.

Note: By default, Zend_Controller_Action::getViewScript() proxies to the ViewRenderer's getViewScript() method.
render($action, $name, $noController) checks first to see if either $name or $noController have been passed, and if so, sets the appropriate flags (responseSegment and noController, respectively) in the ViewRenderer. It then passes the $action argument, if any, on to getViewScript(). Finally, it passes the calculated view script path to renderScript().

Note: Be aware of the side-effects of using render(): the values you pass for the response segment name and for the noController flag will persist in the object. Additionally, noRender will be set after rendering is completed.
Note: By default, Zend_Controller_Action::render() proxies to the ViewRenderer's render() method.
renderBySpec($action, $vars, $name) allows you to pass path specification variables in order to determine the view script path to create. It passes $action and $vars to getScriptPath(), and then passes the resulting script path and $name on to renderScript().

Basic Usage Examples
Example #9 Basic Usage

At its most basic, you simply initialize and register the ViewRenderer helper with the helper broker in your bootstrap, and then set variables in your action methods.

// In your bootstrap:
Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer');
 
...
 
// 'foo' module, 'bar' controller:
class Foo_BarController extends Zend_Controller_Action
{
    // Render bar/index.phtml by default; no action required
    public function indexAction()
    {
    }
 
    // Render bar/populate.phtml with variable 'foo' set to 'bar'.
    // Since view object defined at preDispatch(), it's already available.
    public function populateAction()
    {
        $this->view->foo = 'bar';
    }
 
    // Renders nothing as it forwards to another action; the new action
    // will perform any rendering
    public function bazAction()
    {
        $this->_forward('index');
    }
 
    // Renders nothing as it redirects to another location
    public function batAction()
    {
        $this->_redirect('/index');
    }
}

Note: Naming Conventions: Word Delimiters in Controller and Action Names
If your controller or action name is composed of several words, the dispatcher requires that these are separated on the URL by specific path and word delimiter characters. The ViewRenderer replaces any path delimiter found in the controller name with an actual path delimiter ('/'), and any word delimiter found with a dash ('-') when creating paths. Thus, a call to the action /foo.bar/baz.bat would dispatch to FooBarController::bazBatAction() in FooBarController.php, which would render foo-bar/baz-bat.phtml; a call to the action /bar_baz/baz-bat would dispatch to Bar_BazController::bazBatAction() in Bar/BazController.php (note the path separation) and render bar/baz/baz-bat.phtml.
Note that the in the second example, the module is still the default module, but that, because of the existence of a path separator, the controller receives the name Bar_BazController, in Bar/BazController.php. The ViewRenderer mimics the controller directory hierarchy.
Example #10 Disabling Autorender

For some actions or controllers, you may want to turn off the autorendering -- for instance, if you're wanting to emit a different type of output (XML, JSON, etc), or if you simply want to emit nothing. You have two options: turn off all cases of autorendering ( setNeverRender()), or simply turn it off for the current action ( setNoRender()).

// Baz controller class, bar module:
class Bar_BazController extends Zend_Controller_Action
{
    public function fooAction()
    {
        // Don't auto render this action
        $this->_helper->viewRenderer->setNoRender();
    }
}
 
// Bat controller class, bar module:
class Bar_BatController extends Zend_Controller_Action
{
    public function preDispatch()
    {
        // Never auto render this controller's actions
        $this->_helper->viewRenderer->setNoRender();
    }
}

With this example above, all the file ./views/scripts/baz/foo.phtml will not be displayed.
But if we add some data to display directly into this fooAction(), it will be displayed!
For example with an echo.
There is no sense in a MVC pattern to do this yet.

Example #11 Choosing a Different View Script

Some situations require that you render a different script than one named after the action. For instance, if you have a controller that has both add and edit actions, they may both display the same 'form' view, albeit with different values set. You can easily change the script name used with either setScriptAction(), setRender(), or calling the helper as a method, which will invoke setRender().

// Bar controller class, foo module:
class Foo_BarController extends Zend_Controller_Action
{
    public function addAction()
    {
        // Render 'bar/form.phtml' instead of 'bar/add.phtml'
        $this->_helper->viewRenderer('form');
    }
 
    public function editAction()
    {
        // Render 'bar/form.phtml' instead of 'bar/edit.phtml'
        $this->_helper->viewRenderer->setScriptAction('form');
    }
 
    public function processAction()
    {
        // do some validation...
        if (!$valid) {
            // Render 'bar/form.phtml' instead of 'bar/process.phtml'
            $this->_helper->viewRenderer->setRender('form');
            return;
        }
 
        // otherwise continue processing...
    }
 
}

Example #12 Modifying the Registered View

What if you need to modify the view object -- for instance, change the helper paths, or the encoding? You can do so either by modifying the view object set in your controller, or by grabbing the view object out of the ViewRenderer; both are references to the same object.

// Bar controller class, foo module:
class Foo_BarController extends Zend_Controller_Action
{
    public function preDispatch()
    {
        // change view encoding
        $this->view->setEncoding('UTF-8');
    }
 
    public function bazAction()
    {
        // Get view object and set escape callback to 'htmlspecialchars'
        $view = $this->_helper->viewRenderer->view;
        $view->setEscape('htmlspecialchars');
    }
}

Advanced Usage Examples
Example #13 Changing the Path Specifications

In some circumstances, you may decide that the default path specifications do not fit your site's needs. For instance, you may want to have a single template tree to which you may then give access to your designers (this is very typical when using » Smarty, for instance). In such a case, you may want to hardcode the view base path specification, and create an alternate specification for the action view script paths themselves.

For purposes of this example, let's assume that the base path to views should be '/opt/vendor/templates', and that you wish for view scripts to be referenced by ':moduleDir/:controller/:action.:suffix'; if the noController flag has been set, you want to render out of the top level instead of in a subdirectory (':action.:suffix'). Finally, you want to use 'tpl' as the view script filename suffix.

/**
* In your bootstrap:
*/

// Different view implementation
$view = new ZF_Smarty();

$viewRenderer = new Zend_Controller_Action_Helper_ViewRenderer($view);
$viewRenderer->setViewBasePathSpec('/opt/vendor/templates')
             ->setViewScriptPathSpec(':module/:controller/:action.:suffix')
             ->setViewScriptPathNoControllerSpec(':action.:suffix')
             ->setViewSuffix('tpl');
Zend_Controller_Action_HelperBroker::addHelper($viewRenderer);

Example #14 Rendering Multiple View Scripts from a Single Action

At times, you may need to render multiple view scripts from a single action. This is very straightforward -- simply make multiple calls to render():

class SearchController extends Zend_Controller_Action
{
    public function resultsAction()
    {
        // Assume $this->model is the current model
        $this->view->results =
            $this->model->find($this->_getParam('query', '');

        // render() by default proxies to the ViewRenderer
        // Render first the search form and then the results
        $this->render('form');
        $this->render('results');
    }

    public function formAction()
    {
        // do nothing; ViewRenderer autorenders the view script
    }
}

Writing Your Own Helpers

Action helpers extend Zend_Controller_Action_Helper_Abstract, an abstract class that provides the basic interface and functionality required by the helper broker. These include the following methods:

setActionController() is used to set the current action controller.

init(), triggered by the helper broker at instantiation, can be used to trigger initialization in the helper; this can be useful for resetting state when multiple controllers use the same helper in chained actions.

preDispatch(), is triggered prior to a dispatched action.

postDispatch() is triggered when a dispatched action is done -- even if a preDispatch() plugin has skipped the action. Mainly useful for cleanup.

getRequest() retrieves the current request object.

getResponse() retrieves the current response object.

getName() retrieves the helper name. It retrieves the portion of the class name following the last underscore character, or the full class name otherwise. As an example, if the class is named Zend_Controller_Action_Helper_Redirector, it will return Redirector; a class named FooMessage will simply return itself.

You may optionally include a direct() method in your helper class. If defined, it allows you to treat the helper as a method of the helper broker, in order to allow easy, one-off usage of the helper. As an example, the redirector defines direct() as an alias of goto(), allowing use of the helper like this:

// Redirect to /blog/view/item/id/42
$this->_helper->redirector('item', 'view', 'blog', array('id' => 42));

Internally, the helper broker's __call() method looks for a helper named redirector, then checks to see if that helper has a defined direct() method, and calls it with the arguments provided.

Once you have created your own helper class, you may provide access to it as described in the sections above.

The response object

The response object is the logical counterpart 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 sendResponse().

$response->sendResponse();

Let's take an example to retrieve the response object:
I created the MenuController with a scripts/menu/index.phtml, then a FooController with a scripts/foo/bar.phtml.

// IndexController
public function indexAction()
    {
        $response = $this->getResponse();
        $response->insert('menu', $this->view->render('menu/index.phtml'));
        $response->insert('foo', $this->view->render('foo/bar.phtml'));
        $this->view->myResponse = $response;
    }
// index/index.phtml
<?php echo $this->myResponse; ?>
// foo/bar.phtml
<?php echo "I am in the foo/bar.phtml</br>" ; ?>
// menu/index.phtml
<?php echo "I am in the menu/index.phtml</br>"; ?>

As a result displayed, we will have:

// http://myproject/
I am in the menu/index.phtml
I am in the foo/bar.phtml

Note: By default, the front controller calls sendResponse() when it has finished dispatching the request; typically you will never need to call it. However, if you wish to manipulate the response or use it in testing, you can override this behaviour by setting the returnResponse flag with Zend_Controller_Front::returnResponse(true):

$front->returnResponse(true);
$response = $front->dispatch();
 
// do some more processing, such as logging...
// and then send the output:
$response->sendResponse();

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.

Note: If using the action controller view integration, you do not need to set the rendered view script content in the response object, as Zend_Controller_Action::render() does this by default.
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->sendResponse();
 
// or:
$front->throwExceptions(true);
$front->dispatch();

Manipulating Headers

As stated previously, one aspect of the response object's duties is to collect and emit HTTP response headers. A variety of methods exist for this:

canSendHeaders() is used to determine if headers have already been sent. It takes an optional flag indicating whether or not to throw an exception if headers have already been sent. This can be overridden by setting the property headersSentThrowsException to FALSE.

setHeader($name, $value, $replace = false) is used to set an individual header. By default, it does not replace existing headers of the same name in the object; however, setting $replace to TRUE will force it to do so.

Before setting the header, it checks with canSendHeaders() to see if this operation is allowed at this point, and requests that an exception be thrown.

setRedirect($url, $code = 302) sets an HTTP Location header for a redirect. If an HTTP status code has been provided, it will use that status code.

Internally, it calls setHeader() with the $replace flag on to ensure only one such header is ever sent.

getHeaders() returns an array of all headers. Each array element is an array with the keys 'name' and 'value'.

clearHeaders() clears all registered headers.

setRawHeader() can be used to set headers that are not key and value pairs, such as an HTTP status header.

getRawHeaders() returns any registered raw headers.

clearRawHeaders() clears any registered raw headers.

clearAllHeaders() clears both regular key and value headers as well as raw headers.

In addition to the above methods, there are accessors for setting and retrieving the HTTP response code for the current request, setHttpResponseCode() and getHttpResponseCode().

Named Segments

The response object has support for "named segments". This allows you to segregate body content into different segments and order those segments so output is returned in a specific order. Internally, body content is saved as an array, and the various accessor methods can be used to indicate placement and names within that array.

As an example, you could use a preDispatch() hook to add a header to the response object, then have the action controller add body content, and a postDispatch() hook add a footer:

// Assume that this plugin class is registered with the front controller
class MyPlugin extends Zend_Controller_Plugin_Abstract
{
    public function preDispatch(Zend_Controller_Request_Abstract $request)
    {
        $response = $this->getResponse();
        $view = new Zend_View();
        $view->setBasePath('../views/scripts');
 
        $response->prepend('header', $view->render('header.phtml'));
    }
 
    public function postDispatch(Zend_Controller_Request_Abstract $request)
    {
        $response = $this->getResponse();
        $view = new Zend_View();
        $view->setBasePath('../views/scripts');
 
        $response->append('footer', $view->render('footer.phtml'));
    }
}
// a sample action controller
class MyController extends Zend_Controller_Action
{
    public function fooAction()
    {
        $this->render();
    }
}

In the above example, a call to /my/foo will cause the final body content of the response object to have the following structure:

array(
    'header'  => ..., // header content
    'default' => ..., // body content from MyController::fooAction()
    'footer'  => ...  // footer content
);

When this is rendered, it will render in the order in which elements are arranged in the array.

A variety of methods can be used to manipulate the named segments:

setBody() and appendBody() both allow you to pass a second value, $name, indicating a named segment. In each case, if you provide this, it will overwrite that specific named segment or create it if it does not exist (appending to the array by default). If no named segment is passed to setBody(), it resets the entire body content array. If no named segment is passed to appendBody(), the content is appended to the value in the 'default' name segment.

prepend($name, $content) will create a segment named $name and place it at the beginning of the array. If the segment exists already, it will be removed prior to the operation (i.e., overwritten and replaced).

append($name, $content) will create a segment named $name and place it at the end of the array. If the segment exists already, it will be removed prior to the operation (i.e., overwritten and replaced).

insert($name, $content, $parent = null, $before = false) will create a segment named $name. If provided with a $parent segment, the new segment will be placed either before or after that segment (based on the value of $before) in the array. If the segment exists already, it will be removed prior to the operation (i.e., overwritten and replaced).

clearBody($name = null) will remove a single named segment if a $name is provided (and the entire array otherwise).

getBody($spec = false) can be used to retrieve a single array segment if $spec is the name of a named segment. If $spec is FALSE, it returns a string formed by concatenating all named segments in order. If $spec is TRUE, it returns the body content array.

Testing for Exceptions in the Response Object

As mentioned earlier, by default, exceptions caught during dispatch are registered with the response object. Exceptions are registered in a stack, which allows you to keep all exceptions thrown -- application exceptions, dispatch exceptions, plugin exceptions, etc. Should you wish to check for particular exceptions or to log exceptions, you'll want to use the response object's exception API:

setException(Exception $e) allows you to register an exception.

isException() will tell you if an exception has been registered.

getException() returns the entire exception stack.

hasExceptionOfType($type) allows you to determine if an exception of a particular class is in the stack.

hasExceptionOfMessage($message) allows you to determine if an exception with a specific message is in the stack.

hasExceptionOfCode($code) allows you to determine if an exception with a specific code is in the stack.

getExceptionByType($type) allows you to retrieve all exceptions of a specific class from the stack. It will return FALSE if none are found, and an array of exceptions otherwise.

getExceptionByMessage($message) allows you to retrieve all exceptions with a specific message from the stack. It will return FALSE if none are found, and an array of exceptions otherwise.

getExceptionByCode($code) allows you to retrieve all exceptions with a specific code from the stack. It will return FALSE if none are found, and an array of exceptions otherwise.

renderExceptions($flag) allows you to set a flag indicating whether or not exceptions should be emitted when the response is sent.

Subclassing the Response Object

The purpose of the response object is to collect headers and content from the various actions and plugins and return them to the client; secondarily, it also collects any errors (exceptions) that occur in order to process them, return them, or hide them from the end user.

The base response class is Zend_Controller_Response_Abstract, and any subclass you create should extend that class or one of its derivatives. The various methods available have been listed in the previous sections.

Reasons to subclass the response object include modifying how output is returned based on the request environment (e.g., not sending headers for CLI or PHP-GTK requests), adding functionality to return a final view based on content stored in named segments, etc.

I hope it helped you. cool

Comments

Comment: 

Hi,

I am jsut started looking each section

It looks really good.

found a Typo. You have mentioned

Zend_Front_Controller

instead of

Zend_Controller_Front

 

Comment: 

Done!

Thank you so much Anees. wink

Comment: 

Very useful article Mi-K
this will save lot of time and money for developer :)

Add new comment

Plain text

  • No HTML tags allowed.
  • Lines and paragraphs break automatically.