> Zend Framework中文手册 > 7.8. 动作助手

7.8. 动作助手

7.8.1. 介绍

动作助手可以向任何Zend_Controller_Action的衍生动作控制器中,即时的加入功能(runtime and/or on-demand functionality),以使得增加公共的动作控制器功能时,尽量减少衍生动作控制器类的必要。

动作助手有多种使用方式。动作助手使用了一套经纪系统(brokerage system),与Zend_View_Helper中使用的,也就是Zend_Controller_Plugin的经纪系统类似。动作助手(like Zend_View_Helper)在需要调用时加载,可以在请求的时候(bootstrap)或者动作控制器创建的时候(init())实例化。要充分了解这些细节,请阅读下面的章节。

7.8.2. 初始化助手

根据需求以及助手的功能,可有几种不同的初始化方式。

助手经纪人(broker)存储在Zend_Controller_Action$_helper成员中,可以使用经纪人来获取或者调用助手。以下是操作方法:

  • 显式的调用getHelper()方法。简单的传入助手名字,就可以返回助手对象。

    $flashMessenger = $this->_helper->getHelper('FlashMessenger');
    $flashMessenger->addMessage('We did something in the last request');
    
                    
  • 使用助手经纪人的__get()方法,就像获取经纪人的成员属性一样获取助手。

    $flashMessenger = $this->_helper->FlashMessenger;
    $flashMessenger->addMessage('We did something in the last request');
    
                    
  • 最后,大部分动作助手实现了direct()方法,它将调用助手的一个特定的,默认的方法。以FlashMessenger为例,它调用了方法addMessage()

    $this->_helper->FlashMessenger('We did something in the last request');
    
                    
7.8. 动作助手 注意

上面的例子在功能上是等价的。

也可以显式的实例化助手。如果你要在动作控制器之外使用助手,或者给助手经纪人传入一个助手供所有的动作使用,你可能希望直接这么做。实例化助手和实例化其他PHP类的方法一样。

7.8.3. 助手经纪人

Zend_Controller_Action_HelperBroker处理注册助手对象和助手路径,即时的获取助手等细节。

使用addHelper来注册助手:

Zend_Controller_Action_HelperBroker::addHelper($helper);

        

实例化助手并传入经纪人有点耗费时间和资源,不过addPrefix()addPath()两个方法能够很容易的自动完成这些工作:

  • addPrefix()方法带有一个类前缀参数,用来加入自定义助手类的路径。它假定前缀遵循Zend Framework的类命名惯例。

    // Add helpers prefixed with My_Action_Helpers in My/Action/Helpers/
    Zend_Controller_Action_HelperBroker::addPrefix('My_Action_Helpers');
    
                    
  • addPath()方法第一个参数为一个目录,第二个为类前缀(默认为'Zend_Controller_Action_Helper')。用来将自己的类前缀映射到指定的目录。

    // Add helpers prefixed with Helper in Plugins/Helpers/
    Zend_Controller_Action_HelperBroker::addPath('./Plugins/Helpers', 
                                                 'Helper');
    
                    

这些方法是静态的,因而可以根据需要在控制器链中的任何位置调用动态的加载助手。

使用hasHelper($name)方法来判定助手经纪人中是否存在某助手,$name是助手的短名称(去掉前缀的):

// Check if 'redirector' helper is registered with the broker:
if (Zend_Controller_Action_HelperBroker::hasHelper('redirector')) {
    echo 'Redirector helper registered';
}

        

从助手经纪人中获取助手有两个静态方法:getExistingHelper()getStaticHelper()getExistingHelper()将获取助手仅当它以前调用过或者显性地通过助手经纪人注册过,否则就抛出一个异常。getStaticHelper() 的做法和getExistingHelper()一样,但如果还没有注册助手堆栈,它将尝试初始化助手,为获取你要配置的的助手,getStaticHelper()是一个好的选择。

两个方法都带一个参数,$name,它是助手的短名称(去掉前缀)。

// Check if 'redirector' helper is registered with the broker, and fetch:
if (Zend_Controller_Action_HelperBroker::hasHelper('redirector')) {
    $redirector = 
        Zend_Controller_Action_HelperBroker::getExistingHelper('redirector');
}

// Or, simply retrieve it, not worrying about whether or not it was 
// previously registered:
$redirector = 
    Zend_Controller_Action_HelperBroker::getStaticHelper('redirector');
}

        

最后,使用removeHelper($name)来删除助手经纪人中的某个助手,$name是助手的短名称。

// Conditionally remove the 'redirector' helper from the broker:
if (Zend_Controller_Action_HelperBroker::hasHelper('redirector')) {
    Zend_Controller_Action_HelperBroker::removeHelper('redirector')
}

        

7.8.4. 内建的动作助手

Zend Framework 默认包含若干个动作助手:AutoComplete 自动响应 AJAX 的自动完成;ContextSwitchAjaxContext 为你的动作提供替代响应格式;FlashMessenger 用来处理Flash Messenger会话;Json 用来解码和发送 JSON 响应;Redirector 提供另一种实现方式,帮助程序重定向到内部或者外部页面;ViewRenderer 自动的完成在控制器内建立视图对象并渲染视图的过程。

7.8.4.1. 动作堆栈(助手)

动作堆栈助手允许把请求压到动作堆栈前端控制器插件,有效地帮助你在请求期间创建一个动作队列来执行。(动作堆栈)助手允许你通过指定新的请求对象或通过“动作/控制器/模块”集合来添加动作。

7.8. 动作助手 调用动作堆栈助手来初始化动作堆栈插件

调用动作堆栈 助手暗中注册动作堆栈 插件 -- 这就意味着你不需要显性地注册动作堆栈 插件来用这个功能。

例 7.2. 用动作、控制器和模块名来添加一个任务

经常地,仅仅指定动作、控制器和模块(和可选的参数)最简单,和调用Zend_Controller_Action::_forward()一样:

class FooController extends Zend_Controller_Action
{
    public function barAction()
    {
        // Add two actions to the stack
        // Add call to /foo/baz/bar/baz
        // (FooController::bazAction() with request var bar == baz)
        $this->_helper->actionStack('baz', 
                                    'foo', 
                                    'default', 
                                    array('bar' => 'baz'));

        // Add call to /bar/bat
        // (BarController::batAction())
        $this->_helper->actionStack('bat', 'bar');
    }
}

        

例 7.3. 使用请求对象添加一个任务

有时候请求对象的OOP本性很有用;你也可以传递这样一个对象给动作堆栈助手。

class FooController extends Zend_Controller_Action
{
    public function barAction()
    {
        // Add two actions to the stack
        // Add call to /foo/baz/bar/baz
        // (FooController::bazAction() with request var bar == baz)
        $request = clone $this->getRequest();
        // Don't set controller or module; use current values
        $request->setActionName('baz')               
                ->setParams(array('bar' => 'baz')); 
        $this->_helper->actionStack($request);

        // Add call to /bar/bat
        // (BarController::batAction())
        $request = clone $this->getRequest();
        // don't set module; use current value
        $request->setActionName('bat')      
                ->setControllerName('bar'); 
        $this->_helper->actionStack($request);
    }
}

        

7.8.4.2. AutoComplete

许多 AJAX javascript 函数库提供了以潜在地匹配结果的选择列表作为用户类别显示的自动完成。AutoComplete 助手简化返回可接受的响应给这样的方法。

因为并非所有的 JS 库用同样的方法实现自动完成,AutoComplete 助手提供了一些必要的基本的摘要函数给这些库并对个别库提供了具体实现。返回类型一般是 JSON 字符串数组、JSON 数组的数组 (每个成员的数组是一个用于创建选择列表的元数据(metadata)的关联数组) 或 HTML。

每个实现的基本用法都一样:

class FooController extends Zend_Controller_Action
{
    public function barAction()
    {
        // Perform some logic...

        // Encode and send response;
        $this->_helper->autoCompleteDojo($data);

        // Or explicitly:
        $response = $this->_helper->autoCompleteDojo
                                  ->sendAutoCompletion($data);

        // Or simply prepare autocompletion response:
        $response = $this->_helper->autoCompleteDojo
                                  ->prepareAutoCompletion($data);
    }
}

        

缺省地,自动完成做这些工作:

  • 关闭布局(layouts)和视图解析器(ViewRenderer)。

  • 设置合适的响应头。

  • 用编码的/格式化的自动完成数据设置响应体。

  • 发送响应。

可用的助手方法包括:

  • disableLayouts() 用来关闭布局和视图解析器。一般地,在 prepareAutoCompletion() 里调用它。

  • encodeJson($data, $keepLayouts = false) 给 JSON 编码,可选打开或关闭布局。一般地,在 prepareAutoCompletion() 里调用它。

  • prepareAutoCompletion($data, $keepLayouts = false) 用来为具体实现以响应格式准备数据,可选地打开或关闭布局,返回值因不同的实现而不同。

  • sendAutoCompletion($data, $keepLayouts = false) 用来为具体实现发送响应格式的数据,它调用 prepareAutoCompletion(),然后发送响应。

  • 当把助手作为助手代理的方法来调用,使用 direct($data, $sendNow = true, $keepLayouts = false) 。The $sendNow flag 用来决定是否分别调用 sendAutoCompletion()prepareAutoCompletion()

目前 AutoComplete 支持 Dojo 和 Scriptaculous AJAX 库。

7.8.4.2.1. AutoCompletion with Dojo

Dojo 本身没有 AutoCompletion 小部件,但有两个小部件可以执行 AutoCompletion:ComboBox 和 FilteringSelect。对于这两者,都要求实现 QueryReadStore 的数据存储,关于这话题的更多信息参见 dojo.data 文档。

在 Zend Framework 中,可以传递简单的索引的数组给 AutoCompleteDojo 助手,它将返回一个适合和这样一个存储一起使用的 JSON 响应:

// within a controller action:
$this->_helper->autoCompleteDojo($data);

        

例 7.4. AutoCompletion with Dojo Using Zend MVC

AutoCompletion with Dojo 通过 Zend MVC 需要若干事项:为你想要 AutoCompletion 的 ComboBox 生成一个表单对象,服务于 AutoCompletion 结果的控制器动作,生成定制的 QueryReadStore 来连接到 AutoCompletion 动作和 javascript 的生成用于在服务器端初始化 AutoCompletion。

首先,看一下必需的 javascript 。Dojo 为生成 OOP javascript 提供一个完整的框架,很像 Zend Framework 对于 PHP。它的部分功能是使用目录等级结构生成假的命名空间(pseudo-namespaces )。 我们将在和 Dojo 同一级目录创建一个 'custom' 目录,那是 Dojo 发行的一部分。 在目录里面,我们将创建 javascript 文件,TestNameReadStore.js 带有以下内容:

dojo.provide("custom.TestNameReadStore");
dojo.declare("custom.TestNameReadStore", dojox.data.QueryReadStore, {
    fetch:function (request) {
        request.serverQuery = { test:request.query.name };
        return this.inherited("fetch", arguments);
    }
});

该类是 Dojo 自己的 QueryReadStore 的扩展,QueryReadStore 是一个抽象类。我们简单地通过请求定义一个方法,并把它分配给 'test' 元素。

下一步,为我们想要的 AutoCompletion 生成表单元素:

class TestController extends Zend_Controller_Action
{
    protected $_form;

    public function getForm()
    {   
        if (null === $this->_form) {
            $this->_form = new Zend_Form();
            $this->_form->setMethod('get')
                ->setAction(
                    $this->getRequest()->getBaseUrl() . '/test/process'
                )
                ->addElements(array(
                    'test' => array('type' => 'text', 'options' => array(
                        'filters'        => array('StringTrim'),
                        'dojoType'       => array('dijit.form.ComboBox'),
                        'store'          => 'testStore',
                        'autoComplete'   => 'false',
                        'hasDownArrow'   => 'true',
                        'label' => 'Your input:',
                    )),
                    'go' => array('type' => 'submit', 
                                  'options' => array('label' => 'Go!'))
                ));
        }
        return $this->_form;
    }
}

            

这里,我们用 'test' 和 'go' 方法生成表单。'test' 方法添加若干特别的 Dojo 专用的属性:dojoType、 store、 autoComplete 和 hasDownArrow。dojoType 用来指示我们在生成 comboBox,并且我们将把它链接到 'testStore' 的数据存储(键 'store')-- 稍后还有更多。指定 'autoComplete' 作为 false 告诉 Dojo 不要自动选择第一个匹配,但是要显示一个匹配列表。最后,'hasDownArrow' 生成和选择 box 类似的向下箭头,这样我们可以显示和隐藏匹配。

让我们添加一个方法来显示表单,和处理 AutoCompletion 的结束点:

class TestController extends Zend_Controller_Action
{
    // ...

    /**
     * Landing page
     */
    public function indexAction()
    {
        $this->view->form = $this->getForm();
    }

    public function autocompleteAction()
    {
        if ('ajax' != $this->_getParam('format', false)) {
            return $this->_helper->redirector('index');
        }
        if ($this->getRequest()->isPost()) {
            return $this->_helper->redirector('index');
        }

        $match = trim($this->getRequest()->getQuery('test', ''));

        $matches = array();
        foreach ($this->getData() as $datum) {
            if (0 === strpos($datum, $match)) {
                $matches[] = $datum;
            }
        }
        $this->_helper->autoCompleteDojo($matches);
    }
}

            

autocompleteAction() 中我们做许多事情。首先,我们注意确保我们有个 post 请求,并且有个 'format' 参数的值为 'ajax';这样减少欺骗查询给动作。接着,我们检查 'test' 参数,并和我们的数据比较。(我在这里故意忽略了 getData() 的实现 -- 它可以是任何数据源)。最后,发送匹配给 AutoCompletion 助手。

既然我们在后台有了所有的东西,来看一下在视图脚本中对于 landing 页面我们需要提交什么。 首先,我们需要设置数据存储,然后解析表单,最后确保合适的 Dojo 库被加载 --包括定制的数据存储。来看看视图脚本,步骤在注释里:

<? // setup our data store: ?>
<div dojoType="custom.TestNameReadStore" jsId="testStore"
    url="<?= $this->baseUrl() ?>/unit-test/autocomplete/format/ajax" 
    requestMethod="get"></div>

<? // render our form: ?>
<?= $this->form ?>

<? // setup Dojo-related CSS to load in HTML head: ?>
<? $this->headStyle()->captureStart() ?>
@import "<?= $this->baseUrl() ?>/javascript/dijit/themes/tundra/tundra.css";
@import "<?= $this->baseUrl() ?>/javascript/dojo/resources/dojo.css";
<? $this->headStyle()->captureEnd() ?>

<? // setup javascript to load in HTML head, including all required 
   // Dojo libraries: ?>
<? $this->headScript()
        ->setAllowArbitraryAttributes(true)
        ->appendFile($this->baseUrl() . '/javascript/dojo/dojo.js', 
            'text/javascript', 
            array('djConfig' => 'parseOnLoad: true'))
        ->captureStart() ?>
djConfig.usePlainJson=true;
dojo.registerModulePath("custom","../custom");
dojo.require("dojo.parser");
dojo.require("dojox.data.QueryReadStore");
dojo.require("dijit.form.ComboBox");
dojo.require("custom.TestNameReadStore");
<? $this->headScript()->captureEnd() ?>

            

注意对视图助手的调用如 headStyle 和 headScript,它们是占位符,我们可以在布局视图脚本的 HTML 头中解析。

现在所有的 Dojo AutoCompletion 开始工作了。


7.8.4.2.2. AutoCompletion with Scriptaculous

Scriptaculous 需要一个特定格式的 HTML 响应。

和这个库一起使用的助手是 'AutoCompleteScriptaculous',给它提供一个数据数组,这个助手将生成兼容于 Ajax.Autocompleter 的 HTML 响应。

7.8.4.3. ContextSwitch and AjaxContext

ContextSwitch 动作助手用来使在请求后返回不同的响应格式变得容易。AjaxContext 助手是 ContextSwitch 的特别版本,用来返回响应到 XmlHttpRequests。

你必须在关于动作能够响应哪个上下文(context)的控制器中打开(enable)其中一个。如果一个进来的请求对给定的动作指出一个有效的上下文,助手将做:

  • 如果布局被打开,则关闭它。

  • 设置一个备用的视图后缀,为上下文有效地请求一个分离的视图脚本。

  • 为期望的上下文发送适当的响应头。

  • 可选地调用特别的回调(callback)来设置上下文和/或执行处理后的任务。

例子,考虑下列的控制器:

class NewsController extends Zend_Controller_Action
{
    /**
     * Landing page; forwards to listAction()
     */
    public function indexAction()
    {
        $this->_forward('list');
    }

    /**
     * List news items
     */
    public function listAction()
    {
    }

    /**
     * View a news item
     */
    public function viewAction()
    {
    }
}

    

我们想让 listAction() 也支持 XML 格式,不用创建一个不同的动作,我们可以提示它可以返回 XML 响应:

class NewsController extends Zend_Controller_Action
{
    public function init()
    {
        $contextSwitch = $this->_helper->getHelper('contextSwitch');
        $contextSwitch->addActionContext('list', 'xml')
                      ->initContext();
    }

    // ...
}

    

这将完成:

  • 设置 'Content-Type' 响应头为 'text/xml'.

  • 修改视图后缀为 'xml.phtml' (或者,如果你使用另外的视图后缀,'xml.[你的后缀]')。

现在你需要创建一个新的视图脚本 'news/list.xml.phtml',它将创建和解析 XML。

为决定一个请求是否应该初始化一个上下文开关(context switch),这个助手检查在请求对象理的令牌。缺省地,它寻找一个 'format' 参数,尽管这是可配置的。这意味着,在大多数情况下,为触发一个上下文开关,你可以添加一个 'format' 参数给你的请求:

  • 通过 URL 参数:/news/list/format/xml (回忆一下,缺省路由模式(schema)允许在动作后跟随任意的键/值对)

  • 通过 GET 参数:/news/list?format=xml

ContextSwitch 允许指定任意的上下文,包括需要修改后缀的、任何应该被发送的响应头和任意的用来初始化和善后的回调(callback)。

7.8.4.3.1.  缺省可用的上下文

缺省地,ContextSwitch 助手的两个上下文可用:json 和 xml。

  • JSON. JSON 上下文设置 'Content-Type' 响应头为 'application/json',设置视图脚本后缀为 'json.phtml'。

    缺省地,不需要视图脚本,它将系列化所有的视图变量,立即发出 JSON 响应。

    通过关闭 auto-JSON serialization 可禁止这个行为:

    $this->_helper->contextSwitch()->setAutoJsonSerialization(false);
    
                    
  • XML. XML 上下文设置 'Content-Type' 响应头为 'text/xml',设置视图脚本后缀为 'xml.phtml'。你需要为这个上下文创建新的视图脚本。

7.8.4.3.2.  创建定制的上下文

有时候,缺省的上下文不够用,例如你需要返回 YAML,或系列化 PHP、RSS 或 ATOM feed 等等,ContextSwitch 正是你需要的东西。

最容易的添加新的上下文的办法是通过 addContext() 方法。这个方法带有两个参数:上下文的名称和一个规范(specification)数组,规范应当包含下列中的一个或多个:

  • suffix: 当在视图解析器里注册时,预先准备的缺省的视图后缀。

  • headers: 作为响应的一部分发送的头/值(header/value)对的数组。

  • callbacks: 数组,包含一个或更多键 'init' 或 'post',指向有效的可用于上下文和善后处理的 PHP 回调(callback)。

    回调的初始化发生在当 ContextSwitch 检测到上下文时,你可以用它来执行任意的应该发生的逻辑。作为例子,当 auto-JSON serialization 是 on 的时候,JSON 上下文使用回调来关闭视图解析器(ViewRenderer)。

    善后处理(post processing)发生在动作的 postDispatch() 程序期间,可用来执行任意的逻辑。作为例子,JSON 上下文使用回调(callback)来决定是否 auto-JSON serialization 是 on;如果是,它系列化视图变量到 JSON 和 发送响应,如果不是,它重新打开(re-enable)视图解析器(ViewRenderer)。

和上下文交互作用的方法:

  • addContext($context, array $spec): 添加新的上下文,如果上下文存在,抛出一个异常。

  • setContext($context, array $spec): 添加新的上下文或重写一个已存在的上下文,和 addContext() 使用相同的规范(specification)。

  • addContexts(array $contexts): 一次添加多个上下文。$contexts 应当是上下文/规范(context/specification)对的数组。如果任何一个上下文存在,就抛出异常。

  • setContexts(array $contexts): 添加和重写存在的上下文(多于一个),和 addContexts() 使用相同的规范。

  • hasContext($context): 如果上下文存在,返回 true,否则返回 false。

  • getContext($context): 通过名称来获取一个单个的上下文,返回一个遵循用于 addContext() 的规范的数组。

  • getContexts(): 获取所有的上下文,返回上下文/规范对的数组。

  • removeContext($context): 通过名称来清除一个单个的上下文,成功返回 true,如果没有发现上下文返回 false。

  • clearContexts(): 清除所有上下文。

7.8.4.3.3.  为每个动作设置上下文

有两个设置可用的上下文的机制:或者在控制器里手工创建数组,或者使用在 ContextSwitch 里的方法来装配它们。

添加动作/上下文关系的基本方法是 addActionContext() 。它有两个参数:上下文被添加到的动作和上下文的名称或上下文数组的其中之一。作为例子,考虑下列的控制器类:

class FooController extends Zend_Controller_Action
{
    public function listAction()
    {
    }

    public function viewAction()
    {
    }

    public function commentsAction()
    {
    }

    public function updateAction()
    {
    }
}

        

假如我们想添加 XML 上下文到 'list'动作、XML 和 JSON 上下文到 'comments' 动作,可以在 init() 方法中完成:

class FooController extends Zend_Controller_Action
{
    public function init()
    {
        $this->_helper->contextSwitch()
             ->addActionContext('list', 'xml')
             ->addActionContext('comments', array('xml', 'json'))
             ->initContext();
    }
}

        

另外,还可以定义数组属性 $contexts

class FooController extends Zend_Controller_Action
{
    public $contexts = array(
        'list'     => array('xml'),
        'comments' => array('xml', 'json')
    );

    public function init()
    {
        $this->_helper->contextSwitch()->initContext();
    }
}

        

上述不但缺少周密考虑,而且有潜在的错误。

下面的方法可用来构造上下文映射:

  • addActionContext($action, $context): 标记一个或多个可用的上下文给一个动作,如果映射已经存在,就追加到那些映射中。$context 可以是一个单个的上下文,或者是一个上下文数组。

    一个上下文的 true 值将标记所有可用的上下文给动作。

    一个 $context 的空值将关闭(disable)所有给定动作的上下文。

  • setActionContext($action, $context): 标记一个或多个上下文对动作可用,如果映射已经存在,它就替换它们。$context 可以是一个单个的上下文,或者是一个上下文数组。

  • addActionContexts(array $contexts): 一次性添加若干动作/上下文对,$contexts 是动作/上下文对的关联数组。它代理 addActionContext(),意味着如果那些动作/上下文对存在,就追加之。

  • setActionContexts(array $contexts): 和 addActionContexts() 一样,但重写已存在的动作/上下文对。

  • hasActionContext($action, $context): 决定一个特定的动作是否有一个给定的上下文。

  • getActionContexts($action = null): 返回或者给定动作的所有上下文,或者所有动作/上下文对。

  • removeActionContext($action, $context): 从给定动作中清除一个或多个上下文。$context 可以是一个单个的上下文,或者是一个上下文数组。

  • clearActionContexts($action = null): 从给定动作或者从有上下文的动作中清除所有上下文。

7.8.4.3.4.  初始化上下文开关

为初始化上下文开关,需要在动作控制器中调用 initContext()

class NewsController extends Zend_Controller_Action
{
    public function init()
    {
        $this->_helper->contextSwitch()->initContext();
    }
}

        

在某些情况下,你想强制使用上下文,例如,如果上下文开关是激活状态,你只想用 XML 上下文,可以通过传递上下文给 initContext() 来完成:

$contextSwitch->initContext('xml');

        
7.8.4.3.5.  另外的功能

用来改变 ContextSwitch 助手行为的方法包括:

  • setAutoJsonSerialization($flag): 缺省地,JSON 上下文将系列化任何视图变量给 JSON 符号并把它作为响应返回。如果想创建自己的响应,你需要关闭它,这需要在调用 initContext() 之前来完成。

    $contextSwitch->setAutoJsonSerialization(false);
    $contextSwitch->initContext();
    
                    

    getAutoJsonSerialization() 来获取 flag 的值。

  • setSuffix($context, $suffix, $prependViewRendererSuffix): 用这个方法,你可以为给定的上下文指定一个不同的后缀。第三个参数用来指示是否用新的后缀预先准备当前视图解析器,这个 flag 缺省为打开。

    传递空值给后缀将导致只有视图解析器的后缀被使用。

  • addHeader($context, $header, $content): 为给定的上下文添加一个响应头,$header 是头的名称,$content 是为这个头传递的值。

    每个上下文可以有多个头,addHeader() 把另外的头添加到头的堆栈。

    如果为上下文指定的 $header 已经存在,就抛出一个异常。

  • setHeader($context, $header, $content): setHeader()addHeader() 一样,但它允许重写已经存在的上下文的头。

  • addHeaders($context, array $headers): 一次性添加多个头到给定的上下文,代理 addHeader(),如果头已经存在,将抛出异常。$headers 是一个头/上下文对的数组。

  • setHeaders($context, array $headers.): 象 addHeaders() 一样,但代理 setHeader(),允许重写已存在的头。

  • getHeader($context, $header): 获取给定上下文的头的值,如果没有发现返回 null。

  • removeHeader($context, $header): 清除一个单个的给定上下文的头。

  • clearHeaders($context, $header): 清除所有给定上下文的头。

  • setCallback($context, $trigger, $callback): 为给定的上下文在给定的触发器设置回调(callback),触发器或者是 'init',或者是 'post'(指明回调将被在上下文初始化时或者派遣后(postDispatch)调用)。 $callback 应当是一个有效的 PHP 回调。

  • setCallbacks($context, array $callbacks): 为给定的上下文设置多个回调,$callbacks 应当是触发器/回调对。事实上,最常用的可注册的回调有两个:一个为初始化用的,一个是做善后处理。

  • getCallback($context, $trigger): 在给定的上下文中从给定的触发器中获取一个回调。

  • getCallbacks($context): 从给定的上下文获取所有的回调,返回一个触发器/回调对数组。

  • removeCallback($context, $trigger): 从给定的触发器和上下文清除一个回调。

  • clearCallbacks($context): 为给定的上下文清除所有的回调。

  • setContextParam($name): 设置请求参数来检查什么时候决定上下文开关是否已经请求,缺省值为 'format',但这个访问器可以用来设置一个备用的值。

    getContextParam() 用来获取当前的值。

  • setAutoDisableLayout($flag): 缺省地,当上下文开关出现,布局是关闭的;这是因为一般布局将只用来返回正常的相应,对备用的(alternate)上下文没有意义。然而,如果想使用布局(也许你对新的上下文有个布局),你可以通过传递一个 false 的值给 setAutoDisableLayout() 来改变它的行为。这应当 调用 initContext() 之前 来做。

    用访问器 getAutoDisableLayout() 来获取这个 flag 的值。

  • getCurrentContext() 可用来确定什么上下文被检测到,如果有的话。如果没有上下文开关发生,或者在调用 initContext() 之前调用它,则返回 null。

7.8.4.3.6. AjaxContext 函数

AjaxContext 助手继承 ContextSwitch,所有 ContextSwitch 函数列表对它也有效,只是有些小小的不同。

首先,它为确定上下文使用不同的动作控制器属性 - $ajaxable。这样你就可以对 AJAX 和 普通的 HTTP 请求使用不同的上下文。AjaxContext 的各种各样 *ActionContext*() 方法将写到这个属性。

其次,由请求对象的 isXmlHttpRequest() 方法来确定,它只有在 XmlHttpRequest 出现后才触发。这样,如果上下文参数 ('format')在请求中传递,但请求没有做成 XmlHttpRequest,不会触发上下文开关。

第三,AjaxContext 添加另外的上下文 - HTML。在这个上下文中,为了区别这个上下文和普通的请求,它设置后缀为 'ajax.phtml'。没有另外的头返回。

例 7.5.  允许动作响应 Ajax 的请求

在下面的例子中,我们允许对动作 'view'、 'form' 和 'process' 的请求响应 AJAX 的请求。在头两个例子 'view' 和 'form',返回不更新页面的 HTML 片段;在最后的例子,返回 JSON。

class CommentController extends Zend_Controller_Action
{
    public function init()
    {
        $ajaxContext = $this->_helper->getHelper('AjaxContext');
        $ajaxContext->addActionContext('view', 'html')
                    ->addActionContext('form', 'html')
                    ->addActionContext('process', 'json')
                    ->initContext();
    }

    public function viewAction()
    {
        // Pull a single comment to view.
        // When AjaxContext detected, uses the comment/view.ajax.phtml 
        // view script.
    }

    public function formAction()
    {
        // Render the "add new comment" form.
        // When AjaxContext detected, uses the comment/form.ajax.phtml 
        // view script.
    }

    public function processAction()
    {
        // Process a new comment
        // Return the results as JSON; simply assign the results as 
        // view variables, and JSON will be returned.
    }
}

            

在客户端,AJAX 库将请求终点 '/comment/view'、 '/comment/form' 和 '/comment/process',并传递 'format' 参数:'/comment/view/format/html'、'/comment/form/format/html' 和 '/comment/process/format/json'。(或者你可以通过查询字符串( query string) 传递参数,如:"?format=json")

假定你的库传递 'X-Requested-With:XmlHttpRequest'头,这些动作将返回适当的响应格式。


7.8.4.4. FlashMessenger

7.8.4.4.1. 简介

FlashMessenger助手允许你传递用户可能需要在下个请求看到的消息。为实现它,FlashMessenger使用Zend_Session_Namespace来存储消息以备将来或下个请求来读取。如果你计划使用Zend_Session或者Zend_Session_Namespace,在引导文件里用Zend_Session::start()初始化,的确是个好主意。(参见Zend_Session文档有更多的关于它的用法的细节。)

7.8.4.4.2. Basic Usage Example

下面的例子展示flash messenger最基本的用法。当动作/some/my 被调用,它添加flash message "Record Saved!",随后的对动作/some/my-next-request的请求将读取它(并也这样删除它)。

class SomeController extends Zend_Controller_Action
{
    /**
     * FlashMessenger
     *
     * @var Zend_Controller_Action_Helper_FlashMessenger
     */
    protected $_flashMessenger = null;

    public function init()
    {
        $this->_flashMessenger = 
            $this->_helper->getHelper('FlashMessenger');
        $this->initView();
    }

    public function myAction()
    {
        /**
         * default method of getting 
         * Zend_Controller_Action_Helper_FlashMessenger instance 
         * on-demand
         */
        $this->_flashMessenger->addMessage('Record Saved!');
    }

    public function myNextRequestAction()
    {
        $this->view->messages = $this->_flashMessenger->getMessages();
        $this->render();
    }
}

        

7.8.4.5. JSON

当处理期望数据表响应的 AJAX 请求,JSON 响应迅速变成选择的响应。JSON 可以立即在客户端被解析,从而快速执行。

JSON 动作助手完成以下任务:

  • 如果布局是打开(enabled)的,则关闭(disable)它。

  • 如果视图解析器(ViewRenderer)是打开的,则关闭它。

  • 设置 'Content-Type' 响应头为 'application/json'。

  • 缺省地,不需要等待动作执行完成,立即返回响应。

用法很简单:或者把它作为助手代理的方法来调用,或者调用 encodeJson()sendJson() 方法的其中之一:

class FooController extends Zend_Controller_Action
{
    public function barAction()
    {
        // do some processing...
        // Send the JSON response:
        $this->_helper->json($data);

        // or...
        $this->_helper->json->sendJson($data);

        // or retrieve the json:
        $json = $this->_helper->json->encodeJson($data);
    }
}

    
7.8. 动作助手 保持布局 (Keeping Layouts)

如果你为 JSON 响应有分离的布局 - 也许把 JSON 封装到一些上下文 - 在 JSON 助手的每个方法接受第二个可选的参数:打开或关闭布局的 flag ,传递一个布尔 true 值将使布局保持打开:

class FooController extends Zend_Controller_Action
{
    public function barAction()
    {
        // Retrieve the json, keeping layouts:
        $json = $this->_helper->json->encodeJson($data, true);
    }
}

        

7.8.4.6. 转向器(Redirector)

7.8.4.6.1. 介绍

转向器(Redirector)助手让你使用一个转向器对象帮助程序重定向到新的URL。与_redirect()方法相比,它具有多项优势。例如能够在转向器对象中预先配置整个站点的行为,或者使用与Zend_Controller_Action::_forward()相似的gotoSimple($action, $controller, $module, $params)接口。

转向器拥有影响重定向行为的大量方法:

  • setCode() 设置重定向过程中使用的HTTP响应码。

  • setExit() 在重定向后强制执行exit()方法。默认已设定。

  • setGotoSimple()设置默认的URL,当没有提供参数给gotoSimple()方法时转向该URL。可以使用类似Zend_Controller_Action::_forward()的API:setGotoSimple($action, $controller = null, $module = null, array $params = array());

  • setGotoRoute()设置基于一个注册路由器的URL。通过传入一个键/值数组和一个路由器名,它将根据路由器的类型和定义来组织URL。

  • setGotoUrl()设置默认的URL,当没有参数传入gotoUrl(),将使用该URL。接受单个URL字符串。

  • setPrependBase()setGotoUrl()gotoUrl()或者gotoUrlAndExit()指定的URL前面,加入请求对象的基地址(base URL)。

  • setUseAbsoluteUri()强制转向器在重定向时使用绝对的URI。当该选项设定后,将使用$_SERVER['HTTP_HOST']$_SERVER['SERVER_PORT']$_SERVER['HTTPS']以及重定向方法指定的URL,来形成一个完整的URI。该选项目前默认关闭,将来的版本可能会默认开启。

此外,转向器中还有大量方法来执行实际的重定向。

  • gotoSimple()使用setGotoSimple()(类似_forward()的API)来构建URL并执行重定向。

  • gotoRoute()使用setGotoRoute()(路由组装route-assembly)来构建URL并执行重定向。

  • gotoUrl()使用setGotoUrl() URL字符串)来构造URL并执行重定向。

最后,你可以在任何时刻使用getRedirectUrl()确定当前的重定向URL。

7.8.4.6.2. 基础用例

例 7.6. 设定选项

这个例子改变了几个选项,包括设定重定向时使用的HTTP状态码为303,重定向时不默认退出,以及定义了默认的URL供重定向使用。

class SomeController extends Zend_Controller_Action
{
    /**
     * Redirector - defined for code completion
     *
     * @var Zend_Controller_Action_Helper_Redirector
     */
    protected $_redirector = null;

    public function init()
    {
        $this->_redirector = $this->_helper->getHelper('Redirector');

        // Set the default options for the redirector
        // Since the object is registered in the helper broker, these 
        // become relevant for all actions from this point forward
        $this->_redirector->setCode(303)
                          ->setExit(false)
                          ->setGotoSimple("this-action", 
                                          "some-controller");
    }

    public function myAction()
    {
        /* do some stuff */

        // Redirect to a previously registered URL, and force an exit 
        // to occur when done:
        $this->_redirector->redirectAndExit();
        return; // never reached
    }
}

            

例 7.7. 使用默认设定

这个例子假定使用默认设定,也就意味着任何重定向将导致立即退出。

// ALTERNATIVE EXAMPLE
class AlternativeController extends Zend_Controller_Action
{
    /**
     * Redirector - defined for code completion
     *
     * @var Zend_Controller_Action_Helper_Redirector
     */
    protected $_redirector = null;

    public function init()
    {
        $this->_redirector = $this->_helper->getHelper('Redirector');
    }

    public function myAction()
    {
        /* do some stuff */

        $this->_redirector
            ->gotoUrl('/my-controller/my-action/param1/test/param2/test2');
        return; // never reached since default is to goto and exit
    }
}

            

例 7.8. 使用goto()_forward()API

gotoSimple()'s API 模拟了Zend_Controller_Action::_forward()。主要的不同在于它通过传入的参数构造URL,使用默认路由器的默认格式:module/:controller/:action/*。然后重定向而不是继续动作链循环。

class ForwardController extends Zend_Controller_Action
{
    /**
     * Redirector - defined for code completion
     *
     * @var Zend_Controller_Action_Helper_Redirector
     */
    protected $_redirector = null;

    public function init()
    {
        $this->_redirector = $this->_helper->getHelper('Redirector');
    }

    public function myAction()
    {
        /* do some stuff */

        // Redirect to 'my-action' of 'my-controller' in the current 
        // module, using the params param1 => test and param2 => test2
        $this->_redirector->gotoSimple('my-action', 
                                       'my-controller', 
                                       null, 
                                       array('param1' => 'test', 
                                             'param2' => 'test2'
                                             )
                                       );
    }
}

            

例 7.9. 通过gotoRoute()使用路由组装(route assembly)

下面的例子使用了路由器的assemble()方法,基于传入参数的关联数组来创建URL。假定下面的路由已经注册:

$route = new Zend_Controller_Router_Route(
    'blog/:year/:month/:day/:id',
    array('controller' => 'archive', 
          'module' => 'blog', 
          'action' => 'view')
);
$router->addRoute('blogArchive', $route);

            

给定一个数组,其中年份为2006,月份为4,日期为24,id为42,据此可以组装URL/blog/2006/4/24/42

class BlogAdminController extends Zend_Controller_Action
{
    /**
     * Redirector - defined for code completion
     *
     * @var Zend_Controller_Action_Helper_Redirector
     */
    protected $_redirector = null;

    public function init()
    {
        $this->_redirector = $this->_helper->getHelper('Redirector');
    }

    public function returnAction()
    {
        /* do some stuff */

        // Redirect to blog archive. Builds the following URL:
        // /blog/2006/4/24/42
        $this->_redirector->gotoRoute(
            array('year' => 2006, 
                  'month' => 4, 
                  'day' => 24, 
                  'id' => 42),
            'blogArchive'
        );
    }
}

            

7.8.4.7. ViewRenderer

7.8.4.7.1. 介绍

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

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

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

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

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

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

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

7.8. 动作助手 注意

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

7.8. 动作助手 注意

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');
    
                        
7.8.4.7.2. API

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

Zend_Controller_Action_HelperBroker::getStaticHelper('viewRenderer');

        

动作控制器第一次实例化时,会触发ViewRenderer创建一个视图对象。动作控制器每次实例化都会调用ViewRendererinit()方法,设定动作控制器的视图属性,并以相对于当前模块的路径为参数调用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选项。可以传入以下任意的标志:neverRendernoRendernoControllerscriptAction,和responseSegment

  • setRender($action = null, $name = null, $noController = false)可以一次设定scriptActionresponseSegmentnoControllerdirect()是它的别名,使得控制器中可以方便的调用。

    // 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);
    
                    
    7.8. 动作助手 注意

    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,它将设置后缀和模块目录作为静态引用和目标给视图解析器 属性。

7.8. 动作助手 缺省查找约定(Conventions)

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

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

    在内部,变形器(inflector) 使用过滤器Zend_Filter_Word_CamelCaseToDashZend_Filter_StringToLower

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

    在内部,inflector 使用过滤器Zend_Filter_Word_CamelCaseToDashZend_Filter_Word_UnderscoreToSeparatorZend_Filter_StringToLower

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

    在内部,inflector 使用过滤器 Zend_Filter_Word_CamelCaseToDashZend_Filter_PregreplaceZend_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参数。

    7.8. 动作助手 注意

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

    7.8. 动作助手 注意

    默认的,Zend_Controller_Action::renderScript()代理ViewRendererrenderScript()方法。

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

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

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

    7.8. 动作助手 注意

    默认的,Zend_Controller_Action::getViewScript()代理ViewRenderergetViewScript()方法。

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

    7.8. 动作助手 注意

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

    7.8. 动作助手 注意

    默认的,Zend_Controller_Action::render()代理 ViewRendererrender()方法。

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

7.8.4.7.3. 基础用法示例

例 7.10. 基本用法

大多数基础使用中,只需在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');
    }
}

            

7.8. 动作助手 命名规则:控制器和动作名中的单词定界符

如果控制器或者动作名称由几个单词组成,分发器要求在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模拟了控制器的目录分层。

例 7.11. 禁用自动解析

对于某些动作和控制器,可能希望关闭自动解析——例如,如果想发送其他类型的输出(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();
    }
}

            

7.8. 动作助手 注意

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

例 7.12. 选择另外的视图脚本

有些情况下需要解析另一个脚本而非以动作命名的脚本。例如,如果你有一个控制器包含增加和编辑两个动作,它们可能都显示相同的'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...
    }

}

            

例 7.13. 修改注册的视图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');
    }
}

            

7.8.4.7.4. 高级用法示例

例 7.14. 修改路径规则

有些情况下,默认的路径规则可能并不适合站点的需要。比如,希望拥有一个单独的模板树供设计人员访问(例如,如果你使用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);

            

例 7.15. 一个动作中解析多个视图脚本

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

            

7.8.5. 编写自己的助手

动作助手继承抽象类Zend_Controller_Action_Helper_Abstract,该类提供了助手经纪人要求的基本接口和功能,包含下列方法:

  • setActionController() 用来设置当前的动作控制器。

  • init(),该方法在实例化时由助手经纪人触发,可用来触发助手的初始化过程;动作链中多个控制器使用相同的助手时,如要恢复状态时将十分有用。

  • preDispatch()分发动作之前触发。

  • postDispatch()分发过程结束时触发——即使preDispatch()插件已经跳过了该动作。清理时大量使用。

  • getRequest() 获取当前的请求对象。

  • getResponse() 获取当前的响应对象。

  • getName() 获取助手名。获取了下划线后面的类名部分,没有下划线则获取类的全名。例如,如果类名为Zend_Controller_Action_Helper_Redirector,他将返回 Redirector,如果类名为FooMessage,将会返回全名。

助手类中还可以包含一个direct()方法,如果定义了该方法,就可以将助手视作助手经纪人的一个方法,以便简单的、一次性的使用助手。例如redirector 定义direct() 作为goto()的别名,就可以这样使用:

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

        

在内部,助手经纪人的__call()方法先寻找名叫redirector的助手,然后检查该助手的direct方法是否定义,然后使用所提供的参数来调用该方法。

如果创建了自己的助手,可以按照前面章节所述的提供相应的访问方法。