7.7. 动作控制器

7.7.1. 简介

Zend_Controller_Action是一个抽象类,当基于模型-视图-控制器(MVC)模式建立网站的时候,你可以用它来为和前端控制器使用一起来实现动作控制器。

为使用Zend_Controller_Action,你需要在实际的控制器类中把它子类化(或者为动作控制器创建你自己的基本类而使它子类化)。最基本的操作是子类化和创建对应于不同动作的动作方法,这些动作是你希望控制器来处理你的站点的动作。Zend_Controller的路由和派遣处理将在你的类里自动发现任何以'Action'结尾的方法作为潜在的控制器动作。

例如,你的类如下定义:


class FooController extends Zend_Controller_Action
{
    public function barAction()
    {
        // do something
    }

    public function bazAction()
    {
        // do something
    }
}

上述 FooController 类(控制器foo)定义了两个动作:barbaz

还有更多的可以被实现,例如定制初始化动作,如果没有动作(或者有个无效动作)被指定,缺省的动作被调用,派遣之前和之后的钩子,以及无数的助手方法。这章是动作控制器功能的一个总览。

[注意] 缺省行为

缺省地,前端控制器激活了ViewRenderer动作助手。这个助手负责把视图对象注入到控制器,同时解析(rendering)视图。通过下面方法之一,可以在动作控制器里禁止它:


<?php
class FooController extends Zend_Controller_Action
{
    public function 
init()
    {
        
// Local to this controller only; affects all actions, as loaded in init:
        
$this->_helper->viewRenderer->setNoRender(true);

        
// Globally:
        
$this->_helper->removeHelper('viewRenderer');

        
// Also globally, but would need to be in conjunction with the local
        // version in order to propagate for this controller:
        
Zend_Controller_Front::getInstance()->setParam('noViewRenderer'true);
    }
}

initView()getViewScript()render()renderScript() 都代理 ViewRenderer,除非助手不在助手经纪人(broker)里或noViewRenderer标志被设置。

通过设置ViewRenderernoRender标记,可以简单地为一个独立的视图禁止解析(rendering):


<?php
class FooController extends Zend_Controller_Action
{
    public function 
barAction()
    {
        
// disable autorendering for this action only:
        
$this->_helper->viewRenderer->setNoRender();
    }
}

禁止ViewRenderer的主要原因是如果你不需要视图对象或者如果你不通过视图脚本(例如,当使用动作控制器来司服网站服务协议如SOAP,XML-RPC或REST)来解析。大多数情况下,你不需要全局地禁止ViewRenderer,只选择性地在个别控制器或动作里禁止它。

7.7.2. 对象初始化

虽然你可以总重写动作控制器的构造函数,我们不建议这么做。Zend_Controller_Action::__construct()执行一些重要的任务,如注册请求和响应对象,还有任何从前端控制器传来的invocation参数。如果你必须重写构造函数,别忘记调用parent::__construct($request, $response, $invokeArgs)

更合适的方法来定制实例化是使用init()方法,它是在__construct()里的最后一个调用任务。例如,如果你想在实例化时连接数据库:


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

7.7.3. 派遣前后的钩子

Zend_Controller_Action指定两个方法,preDispatch()postDispatch(),可能被调用来bookend一个请求的动作。这在很多场合都有用:例如在运行一个动作(通过调用preDispatch()里的_forward(),动作将被跳过)前校验认证和ACLs,或者在网站范围的模板里(postDispatch())替换生成的内容。

7.7.4. 访问器

无数的对象和变量与对象一起注册,并且每个都有访问器方法。

  • 请求对象getRequest()可用来读取调用动作请求对象。

  • 响应对象: getResponse()可用来读取收集最终响应的响应对象。一些典型的调用看起来象这样:

    
    $this->getResponse()->setHeader('Content-Type', 'text/xml');
    $this->getResponse()->appendBody($content);
  • 调用参数:前端控制器可能把参数传给路由器、派遣器和动作控制器。为了读取这些参数,可使用getInvokeArg($key);另外,用getInvokeArgs()读取整个参数列表。

  • 请求参数:请求对象手机请求参数,如任何_GET 或 _POST 参数,或者指定在URL的路径信息里的用户参数。为了读取这些参数,可使用_getParam($key)_getAllParams()。也可以用_setParam()来设置请求参数;当转发到另外的动作时这很有用。

    _hasParam($key)来测试是否一个参数存在(对逻辑分支有用)。

    [注意] 注意

    _getParam()可带有一个可选的第二个参数,如果它不是空的,就包含一个缺省的值。用它在读取值之前来消除对_hasParam() 的调用:

    
    <?php
    // Use default value of 1 if id is not set
    $id $this->_getParam('id'1);

    // Instead of:
    if ($this->_hasParam('id') {
        
    $id $this->_getParam('id');
    } else {
        
    $id 1;
    }

7.7.5. 视图集成

Zend_Controller_Action为视图继承提供了一个初步的灵活的机制。有两个方法来完成这个:initView()render();前者松散地加载$view public 属性,后者基于当前请求的动作来解析视图,它们使用目录层次来决定脚本路径。

7.7.5.1. 视图初始化

initView()初始化视图对象。为了读取视图对象,render()调用initView(),但它可以在任何时候被初始化;缺省地,它用Zend_View对象来组装$view属性,但任何实现Zend_View_Interface的类可以使用。如果$view已经被初始化,它就简单地返回属性。

缺省的实现使用下面假设的目录结构:


applicationOrModule/
    controllers/
        IndexController.php
    views/
        scripts/
            index/
                index.phtml
        helpers/
        filters/

换句话说,视图脚本假定放在views/scripts/子目录,同时假定 views子目录还包含兄弟功能(助手和过滤器)。确定视图脚本名称和路径时,先以 views/scripts/作为基路径,然后加上以视图脚本对应控制器命名的目录。

7.7.5.2. 解析(Rendering)视图

render() 有下列特征:has the following signature:


<?php
string render
(string $action nullstring $name nullbool $noController false);

render()解析视图脚本。如果没有传递参数,它假定请求的脚本是[controller]/[action].phtml.phtml$viewSuffix属性的值)。为$action传递一个值将解析在[controller]子目录中的模板。为用[controller]重写,传递一个true值给$noController。最后,模板被解析到响应对象;如果你希望解析到一个在响应对象里指定的named segment,传递一个值给$name

[注意] 注意

因为控制器和动作名字里可能包含分隔符如'_'、 '.' 和 '-',当决定视图名字时,render()把它们规格化成 '-'.在内部,它使用派遣器的字和路径分隔符来做规格化。这样,对/foo.bar/baz-bat的请求将解析脚本foo-bar/baz-bat.phtml。如果动作方法包含camelCasing,记住当决定视图脚本文件名的时候,这将变成由'-'分隔的字。

一些例子:


<?php
class MyController extends Zend_Controller_Action
{
    public function 
fooAction()
    {
        
// Renders my/foo.phtml
        
$this->render();

        
// Renders my/bar.phtml
        
$this->render('bar');

        
// Renders baz.phtml
        
$this->render('baz'nulltrue);

        
// Renders my/login.phtml to the 'form' segment of the response object
        
$this->render('login''form');

        
// Renders site.phtml to the 'page' segment of the response object; does
        // not use the 'my/' subirectory
        
$this->render('site''page'true);
    }

    public function 
bazBatAction()
    {
        
// Renders my/baz-bat.phtml
        
$this->render();
    }
}

7.7.6. 实用方法

除了访问器和视图继承方法,在动作方法内部里,Zend_Controller_Action有若干实用方法来执行普通的任务(或在派遣的前后)。

  • _forward($action, $controller = null, $module = null, array $params = null) :执行另外一个动作。如果在preDispatch()里调用,当前请求的动作将被跳过来支持新的动作。否则,在当前动作被处理之后,在_forward()请求的动作将被执行。

  • _redirect($url, array $options = array()):重定向到另外一个地方。这个方法用URL和一组可选的选项。缺省地,它执行HTTP 302 重定向。

    选项可包括一个或多个下面这些:

    • exit:是否立即退出。如果被请求,它将干净地关闭任何打开的会话和执行重定向。

      可以用setRedirectExit()访问器在控制器里全局地设置这个选项。

    • prependBase:是否预先考虑基础URL和URL提供的请求对象一起注册。

      使用setRedirectPrependBase()访问器,在控制器里全局地设置这个选项。

    • code:在重定向时要用什么HTTP代码。缺省使用302;可以用从301到306之间的任何代码。

      使用setRedirectCode()访问器,在控制器里全局地设置这个选项。

7.7.7. 继承(Subclassing)动作控制器

为了创建动作控制器,设计上,Zend_Controller_Action 必须被继承。至少,需要定义控制器可能调用的动作方法。

除了为web应用程序创建有用的函数外,你可能发现在不同的控制器里重复同样的设置和实用方法;如果这样,创建一个继承(extends)Zend_Controller_Action 的基础类可能会解决问题。

例 7.1. 如何处理不存在的动作

如果控制器的请求包括一个未定义的动作方法,Zend_Controller_Action::__call()将被调用。__call()当然是PHP中用来重载方法的魔术方法。

缺省地,这个方法抛出一个Zend_Controller_Action_Exception 来表明在控制器里没有发现要求的动作。如果你想让它执行其它操作,应该重写它。

例如,如果你想显示错误信息,可以象下面这样来写:


<?php
class MyController extends Zend_Controller_Action
{
    public function 
__call($method$args)
    {
        if (
'Action' == substr($method, -6)) {
            
// If the action method was not found, render the error template
            
return $this->render('error');
        }

        
// all other methods throw an exception
        
throw new Exception('Invalid method "' $method '" called');
    }
}

另外的可能性就是你可能想转发到缺省控制页面:


<?php
class MyController extends Zend_Controller_Action
{
    public function 
indexAction()
    {
        
$this->render();
    }

    public function 
__call($method$args)
    {
        if (
'Action' == substr($method, -6)) {
            
// If the action method was not found, forward to the index action
            
return $this->_forward('index');
        }

        
// all other methods throw an exception
        
throw new Exception('Invalid method "' $method '" called');
    }
}

为了定制控制器,除了重写__call()以外,本章前面说涉及的初始化、实用程序、访问器、视图和派遣钩子等方法都可以被重写。作为例子,如果把视图对象保存到注册表里,你可能想用象下面的代码来修改initView()


<?php
abstract class My_Base_Controller extends Zend_Controller_Action
{
    public function 
initView()
    {
        if (
null === $this->view) {
            if (
Zend_Registry::isRegistered('view')) {
                
$this->view Zend_Registry::get('view');
            } else {
                
$this->view = new Zend_View();
                
$this->view->setBasePath(dirname(__FILE__) . '/../views');
            }
        }

        return 
$this->view;
    }
}

很希望你能从这章的信息里发现这个特别的组件的灵活性并且用到你的程序和网站里。