# ViewRenderer

## Introduction

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.

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.

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');

## API

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);

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

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

  Once the view has been rendered to the response object, it sets the noRender to prevent accidentally rendering the same view script multiple times.

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

  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()`.

  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.

  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

### 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');
        }
    }

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

### 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();
        }
    }

In most cases, it makes no sense to turn off autorendering globally (ala `setNeverRender()`), as the only thing you then gain from ViewRenderer is the autosetup of the view object.

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

    }

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

### 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);

### 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
        }
    }