# ViewRenderer

## 介绍

视图解析(`ViewRenderer`)助手为实现下列目标设计：

- 不需要在控制器内创建视图对象实例；视图对象将在控制器内自动注册。

- 根据当前的模块自动地设置视图脚本、助手、过滤器路径。指派当前的模块名为助手和过滤器类的类名前缀。

- 为所有分发的控制器和动作创建全局有效的视图对象。

- 允许开发人员为所有控制器设置默认的视图解析选项。

- 加入无需干预自动解析试图脚本的功能。

- 允许开发人员为视图基路径和视图脚本路径创建自己的规范。

如果手动执行`_forward()`、`redirect`、或者`render`时，不会发生自动解析。因为执行这些动作时，等于告诉`ViewRenderer`，你要自己确定输出结果。

`ViewRenderer`助手默认启用。你可以通过前端控制器的`noViewRenderer`方法、设定参数(`$front->setParam('noViewRenderer', true)`)或者从助手经纪人栈(helper broker stack)中移除助手(`Zend_Controller_Action_HelperBroker::removeHelper('viewRenderer')`)等方式禁用该助手。

如希望在分发前端控制器前修改`ViewRenderer`设定，可采用下面的两种方法：

- 创建实例并注册自己的`ViewRenderer`对象，然后传入到助手经纪人。

      $viewRenderer = new Zend_Controller_Action_Helper_ViewRenderer();
      $viewRenderer->setView($view)
                   ->setViewSuffix('php');
      Zend_Controller_Action_HelperBroker::addHelper($viewRenderer);

- 通过助手经纪人即时的初始化并/或获取`ViewRenderer`对象。

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

## API

大多数使用中，只需要简单的创建 `ViewRenderer`对象，然后传入到动作助手经纪人。创建实例并注册的最简单方式是使用助手经纪人的`getStaticHelper()`方法：

    Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer');

动作控制器第一次实例化时，会触发`ViewRenderer`创建一个视图对象。动作控制器每次实例化都会调用`ViewRenderer`的`init()`方法，设定动作控制器的视图属性，并以相对于当前模块的路径为参数调用`addScriptPath()`方法；调用时带有以当前模块命名的类前缀参数，该参数对为该模块定义的所有助手和过滤器类都有效。(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. )

每次执行`postDispatch()`方法，它将为当前动作执行`render()`方法。

例如这个类：

    // 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()

`ViewRenderer`也定义了大量的访问器用来设定和获取视图选项。

- `setView($view)`可以为`ViewRenderer`设定视图对象。以公共类属性`$view`获取设定值。

- `setNeverRender($flag = true)`可以全局的启用或禁用自动解析，也就是对所有控制器都有效。如果设定为true，在所有控制器器内，`postDispatch()`将不会自动调用`render()`。`getNeverRender()`返回当前的设定值。

- `setNoRender($flag = true)` 用来启用或禁用自动解析，如果设置为true，在当前控制器内，`postDispatch()`不会调用`render()`方法。这个设定在`preDispatch()`每次执行时会被重置。`getNoRender()`返回当前的设定值。

- `setNoController($flag = true)`通知`render()`不要再到以控制器命名的子目录中寻找视图脚本。`getNoController()`返回当前值。

- `setNeverController($flag = true)`与`setNoController($flag = true)`相似，但是其在全局范围内有效——也就是说，它不会在每次分发动作时重置。`getNeverController()`返回当前值。

- `setScriptAction($name)`用来指定解析的视图脚本。`$name`是脚本的名字去掉后缀(不带控制器子目录，除非`noController`已开启)。如果没有指定，它将寻找以请求对象中的动作命名的视图脚本。`getScriptAction()`返回当前值。

- `setResponseSegment($name)`用来指定解析到响应对象中的哪个命名片段。如果没有指定，解析到默认片断。`getResponseSegment()`返回当前值。

- `initView($path, $prefix, $options)`可以指定视图的基路径，为助手和过滤器脚本设置类前缀，设定`ViewRenderer`选项。可以传入以下任意的标志：`neverRender`，`noRender`，`noController`， `scriptAction`，和`responseSegment`。

- `setRender($action = null, $name = null, $noController = false)`可以一次设定`scriptAction`、`responseSegment`和`noController`。 `direct()`是它的别名，使得控制器中可以方便的调用。

      // 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()` 和 `direct()`并不会实际解析视图脚本，而是提示`postDispatch()`和`postDispatch()`解析视图。

构造函数允许可选的传入参数视图对象和`ViewRenderer`选项，接受与`initView()`一样的标志(flags)：

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

还有几个额外的方法用来定制路径规则，供确定视图基路径来增加视图对象，确定视图脚本路径查找并解析视图脚本时使用。这些方法每个都带有下面一个或更多的占位符(placehodlers)。

- `:moduleDir` 引用当前模块的基目录（常规的是模块的控制器目录的父目录）。

- `:module` 引用当前的模块名。

- `:controller` 引用当前的控制器名。

- `:action`引用当前的模块名。

- `:suffix` 引用当前的视图脚本后缀（可以通过`setViewSuffix()`来设置）。

控制器路径规则有关的方法:

- `setViewBasePathSpec($spec)`可以改变确定加入到视图对象的基路径的路径规则。默认规则是`:moduleDir/views`。任何时候都可以使用`getViewBasePathSpec()`获取当前的规则。

- `setViewScriptPathSpec($spec)`允许改变确定到达单独的视图脚本路径(去除试图脚本基路径)的路径规则。默认的路径规则是 `:controller/:action.:suffix`。任何时候都可以通过`getViewScriptPathSpec()`获取当前规则。

- `setViewScriptPathNoControllerSpec($spec)`允许改变 `noController`有效时确定到达单独的视图脚本路径(去除试图脚本基路径)的路径规则。默认的规则是`:action.:suffix`，任何时候都可以通过`getViewScriptPathNoControllerSpec()`获取当前规则。

为在路径规范之上精心设计的控制，可以使用Zend_Filter_Inflector。深入地，`视图解析器（ViewRenderer）`已经使用inflector来执行路径映射。为和inflector互动 － 或者设置你自己的或者修改缺省的inflector，下面的方法可以被使用：

- `getInflector()` 将获取inflector。如果在`视图解析器`中不存在， 它用缺省的规则创建一个。

  缺省地，它用静态规则引用和静态目标做为后缀和模块目录；这允许不同的`视图解析器`具备动态修改inflector能力的属性。

- `setInflector($inflector, $reference)` 允许设置定制的inflector和`视图解析器`一起使用。如果`$reference` 是true，它将设置后缀和模块目录作为静态引用和目标给`视图解析器` 属性。

### 缺省查找约定（Conventions）

`视图解析器`做了一些路径标准化使视图脚本查找更容易。缺省规则如下：

- `:module`: 混合词和驼峰词被短横线分开，并整个串变成小写。例如："FooBarBaz" 变成 "foo-bar-baz"。

  在内部，变形器（inflector） 使用过滤器`Zend_Filter_Word_CamelCaseToDash` 和 `Zend_Filter_StringToLower`。

- `:controller`: 混合词和驼峰词被短横线分开；下划线转换成目录分隔符，并且整个串变小写。例如："FooBar" becomes "foo-bar"; "FooBar_Admin" 变成 "foo-bar/admin".

  在内部，inflector 使用过滤器`Zend_Filter_Word_CamelCaseToDash`、`Zend_Filter_Word_UnderscoreToSeparator` 和 `Zend_Filter_StringToLower`。

- `:action`: 混合词和驼峰词被短横线分开；非字母数字字符翻译成短横线，并且整个串变成小写。 例如 "fooBar" 变成 "foo-bar"； "foo-barBaz" 变成 "foo-bar-baz"。

  在内部，inflector 使用过滤器 `Zend_Filter_Word_CamelCaseToDash`、`Zend_Filter_PregReplace` 和 `Zend_Filter_StringToLower`。

`视图解析器` API中的最后一项是关于实际确定视图脚本路径和解析视图的。包括：

- `renderScript($script, $name)`允许解析指定路径的脚本，可选的命名的路径片段。(`renderScript($script, $name)` allows you to render a script with a path you specify, optionally to a named path segment. )使用该方法时，`ViewRenderer`不会自动的确定脚本名称，而是直接的向视图对象的`render()`传入`$script`参数。

  当视图已经被解析到响应对象，将会设置`noRender`阻止相同的脚本被多次解析。

  默认的，`Zend_Controller_Action::renderScript()`代理`ViewRenderer`的`renderScript()`方法。

- `getViewScript($action, $vars)`基于传入的动作和/或`$vars`中的变量创建到视图脚本的路径。该数组中的键可以包含所有的路径指定键('moduleDir','module', 'controller', 'action', and 'suffix')。传入的任何变量都会优先使用，否则利用基于当前请求的值。

  `getViewScript()`根据`noController`标志的设定值使用`viewScriptPathSpec`或者`viewScriptPathNoControllerSpec`。

  模块、控制器以及动作中的单词定界符将后替换成短线('-')。因此，控制器名称'foo.bar'和动作'baz:bat'按照默认的路径规则将会得到视图脚本路径'foo-bar/baz-bat.phtml'。

  默认的，`Zend_Controller_Action::getViewScript()`代理`ViewRenderer`的`getViewScript()`方法。

- `render($action, $name, $noController)`首先检查`$name`或 `$noController`参数是否传入，如果传入，则在ViewRenderer中设定相应的标志(分别是响应片段和noController)。然后传入`$action`参数到`getViewScript()`，最后传入计算的试图脚本路径到`renderScript()`。

  注意使用render()的边际效应：传入的响应片段名称和noController标志在视图对象中存留。此外解析结束后noRender会被设置。

  默认的，`Zend_Controller_Action::render()`代理 `ViewRenderer`的`render()`方法。

- `renderBySpec($action, $vars, $name)`允许传入路径规则变量以确定创建的视图脚本路径。它把`$action`和`$vars`传入到`getScriptPath()`，将脚本路径结果和`$name`传入到`renderScript()`。

## 基础用法示例

### 基本用法

大多数基础使用中，只需在bootstrap中使用助手经纪人简单的初始化和注册`ViewRenderer` 助手，然后在动作方法中设置变量。

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

### 命名规则：控制器和动作名中的单词定界符

如果控制器或者动作名称由几个单词组成，分发器要求在URL中使用特定的路径和单词定界符分隔。`ViewRenderer`创建路径时将控制器名称中的任何路径定界符替换成实际的路径定界符('/')，任何单词定界符替换成短线('-')。对动作`/foo.bar/baz.bat`的调用将分发到`FooBarController.php`中的`FooBarController::bazBatAction()`，然后解析`foo-bar/baz-bat.phtml`；对动作`/bar_baz/baz-bat`的调用将分发到`Bar/BazController.php`中的`Bar_BazController::bazBatAction()`，并解析`bar/baz/baz-bat.phtml`。

注意到在第二个例子中，模块依然是默认的模块，但由于路径分隔符的存在，控制器的接收到的名字为`Bar_BazController`，该类在文件`Bar/BazController.php`中。ViewRenderer模拟了控制器的目录分层。

### 禁用自动解析

对于某些动作和控制器，可能希望关闭自动解析——例如，如果想发送其他类型的输出（XML,JSON等），或者更简单的不想发送任何东西。有两个选项：关闭所有的自动解析(`setNeverRender()`)，或者仅仅关闭当前动作的自动解析(`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();
        }
    }

大多数情况下，全局的关闭自动解析(`setNeverRender()`)没有意义，因为这样`ViewRenderer`做的唯一件事只是自动设置了视图对象。

### 选择另外的视图脚本

有些情况下需要解析另一个脚本而非以动作命名的脚本。例如，如果你有一个控制器包含增加和编辑两个动作，它们可能都显示相同的'form'视图，尽管拥有不同的值集合(value set)。只需要使用`setScriptAction()`或者`setRender()`简单的改变脚本的名称，或者以成员方法的形式调用助手，它将调用`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

如果需要修改视图对象怎么办——例如改变助手路径或者编码？可以在控制器中修改视图对象设定，或者从`ViewRenderer`中抓取视图对象；两种方式引用的是同一个对象。

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

## 高级用法示例

### 修改路径规则

有些情况下，默认的路径规则可能并不适合站点的需要。比如，希望拥有一个单独的模板树供设计人员访问（例如，如果你使用Smarty，这是很典型的情形）。这种情况下，你可能想硬编码视图的基路径规则，为动作视图脚本路径自身创建一套规则。

假定视图的基路径(base path)为'/opt/vendor/templates',希望通过':moduleDir/:controller/:action.:suffix'引用视图脚本；如果设定了noController标志，想在顶级而不是在子目录中解析(':action.:suffix')。最终希望使用'tpl'作为视图脚本文件的后缀。

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

### 一个动作中解析多个视图脚本

有时可能需要在一个动作中解析多个视图脚本。这个非常简单，多次调用`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
        }
    }