第 7 章 Zend_Controller

目录

7.1. 概述
7.1.1. 简介
7.1.2. Request Object Request对象
7.1.3. Route Process 路由过程
7.1.4. Dispatch Process 分配过程
7.1.5. Response Object 响应对象
7.2. 入门
7.2.1. 简介
7.2.2. 服务器设置
7.2.3. Bootstrap文件
7.2.4. 目录结构
7.2.5. 默认的控制器
7.3. 子类化(继承)
7.3.1. 简介
7.3.2. 约定(规则)
7.3.3. Router 接口
7.3.4. Dispatcher 接口
7.4. Provided Subclasses
7.4.1. Introduction
7.4.2. Zend_Controller_Request_Http
7.4.3. Zend_Controller_Router_Rewrite
7.4.4. Zend_Controller_Response_Http
7.4.5. Zend_Controller_Response_Cli
7.5. Action Controllers
7.5.1. Introduction
7.5.2. Object initialization
7.5.3. Pre- and Post-Dispatch Hooks
7.5.4. Accessors
7.5.5. Utility Methods
7.6. 插件
7.6.1. 简介
7.7. Using a Conventional Modular Directory Structure
7.7.1. Introduction
7.7.2. Specifying Module Controller Directories
7.7.3. Routing to modules
7.7.4. Module or Global Default Controller
7.8. MVC Exceptions
7.8.1. Introduction
7.8.2. How can you handle exceptions?
7.8.3. MVC Exceptions You May Encounter
7.9. Migrating from Previous Versions
7.9.1. Migrating from 0.6.0 to 0.8.0
7.9.2. Migrating from 0.2.0 or before to 0.6.0

7.1. 概述

7.1.1. 简介

Zend_Controller是使用MVC模式来构建一个站点的基础。Zend_Controller体系是一个轻量的,模块化和可扩展的体系。它只提供最核心的必要的部分,允许开发者有很大的自由来灵活地构建自己的站点。使用Zend_Controller的站点,其文件组织和代码结构会比较相似。

Zend_Controller 的工作流(workflow)是通过几个组件来实现的。虽然不需要完全理解这几个组件的含义,如果你对工作流程有点了解是很有帮助的。

  • Zend_Controller_Front是Zend_Controller_Controller体系的组织者,它是FrontController设计模式的实现。Zend_Controller_Front处理服务器接受的所有请求,并最后负责将请求分配给各个ActionController(Zend_Controller_Action)

  • Zend_Controller_Request_Abstract用于处理用户请求,提供各种类方法来设置和获得Controller和action的名称及各种请求的参数。另外,它可以跟踪其中的action是否已经被Zend_Controller_Dispatcher分配。本抽象类的子类可用于封装整个请求环境,允许router从中获取用户请求相关信息,或设置controller和action的名称。

    Zend Framework默认使用Zend_Controller_Request_Http类来处理用户请求,该类可用于访问HTTP请求相关信息(用户请求不一定通过HTTP,那么就需要你自己实现相关的类--Haohappy注),

  • Zend_Controller_Router_Interface用于定义router。路由是将检查用户请求并决定由哪一个controller,和其中哪一个 Action来接受请求的过程。request对象中的Controller,action和可选的参数将被Zend_Controller_Dispatcher处理。路由只发生一次:当请求被服务器接收到时,在分配到第一个控制器之前。 (所谓router,和我们熟知的网络路由器的功能是很相似的,具有判断网络地址和选择路径的功能,这里就是用来定位到某个控制器中的某个方法 --Haohappy注)

    默认的router是Zend_Controller_Router,它将一个Zend_Controller_Request_Http指定的URI分解成controller,action和参数。例如:URLhttp://localhost/foo/bar/key/value将被分解成foo controller, bar action,并带有参数key,参数值为value.

  • Zend_Controller_Dispatcher_Interface接口用于定义dispatcher(分配器,或称调度器、派遣器等)。 “分配”是指从request对象中获取controller和action的名称,并映射到controller文件/类及其中action类方法的过程。如果controller或action不存在,会将请求分配到默认的控制器和方法进行处理。

    实际上分配过程包括初始化controller类和调用类方法。和路由不一样,路由只发生一次,而分配是一个循环发生的过程。如果request对象的分配状态被重设为false,则循环就会重复,调用request中设置的方法。如果request对象的分配状态被设置成true,则分配过程结束。

    默认的dispatcher是Zend_Controller_Dispatcher。它规定了控制器类命名首字母大写,并以Controller结尾,而action方法则是首字母小写,以Action结尾,例如: SomeFooController::barAction. 在例子中,控制器是somefoo,而action是bar.

    另外,在加载一个控制器的时候,你可以指定一个module(模块)。有了module,我们可以将controller放在一个子目录中,而不用全部放在controllers目录下。要使用module,可以在front controller中设置参数 useModules

    						$front->setParam('useModules', true);
    						
    					

    举个例子,看下面的URL:

    						http://example.com/user/news/action
    					
    					

    在上面的例子中,我们指定了'user'模块,并调用其中的news控制器。dispatcher会将其解释成User_NewsController类,并在User/NewsController.php文件中寻找该类。

    Module非常有用,当你想把代码分散到子目录中去的时候,或者使用第三方代码时,或者在不同的应用中重用相同的控制器时。

  • Zend_Controller_Action是最基本的控制器。每个具体的控制器都是从Zend_Controller_Action类继承而来,是Zend_Controller_Action的子类,并且有自己的action方法。

  • Zend_Controller_Response_Abstract定义了基础的响应类,用于收集并返回action的响应,包括响应的头部(header)和主体(body),由于它使用了 __toString()方法,所以可以直接用echo来一次性输出所有header和内容。

    默认的响应类是Zend_Controller_Response_Http,它很适合于HTTP环境下使用。

Zend_Controller的工作流程相当简单。Zend_Controller_Front接收一个请求,然后由Zend_Controller_Router来决定分配给哪个controller。Zend_Controller_Router把URI分解,便于设定请求中的controller和action的名称。Zend_Controller_Front接着进入一个分配循环,调用Zend_Controller_Dispatcher,把dispatcher传给request,来调用请求中指定的具体的(或默认的)controller和action进行处理。在controller结束后,又把控制权交加给 Zend_Controller_Front。如果controller发现需要调用另一个controller(request的分配状态被清零),循环会一直继续直到另一次分配执行完毕。

7.1.2. Request Object Request对象

Request对象是一个简单的“值对象”(value object),在 Zend_Controller_Front和router、dispatcher和controller间传递。它封装了controller、action名称及要传递给某个action的参数,还有请求环境中的其它信息,请求环境可以是HTTP、命令行或PHP-GTK等。

  • controller的名称可以通过getControllerName()setControllerName()来访问和设置。

  • action的名称可以通过getActionName()setActionName()来访问和设置。

  • 传递给action的参数是一个关联数组,可以通过getParams()setParams()访问和设置,或者只访问和设置其中一个参数,可以用getParam()setParam()

根据请求的不同类型,不同的请求类中可能有各种可用的方法,例如默认的请求类是Zend_Controller_Request_Http,它有一些用于获取请求URI、路径信息,$_GET和$_POST参数等的类方法。

request对象被传递给front controller,或者如果没有提供request对象,会自动在分配过程一开始时实例化生成,在路由发生之前。request对象会被传递给dispatch循环中的每个对象。

另外,request对象在测试时非常有用。程序员可能会构造请求环境,包括controller、action、参数、URI等,并传递请求对象到front controller,来测试整个程序的工作流。同时使用request和response对象,精细和准确的单元测试将变成可能。

7.1.3. Route Process 路由过程

在你构建第一个控制器之前,你需要理解Zend_Controller_Router中的重定向过程是如何工作的。记住工作流程分为两步:一是路由(routing),只发生一次;二是分配(dispatching),循环过程。

Zend_Controller_Front 调用Zend_Controller_Router(或者你自己注册的router)来使一个URI映射到一个controller及其中的action上。Zend_Controller_Router从request对象中获取URI,并分解之,决定将调用的controller、action和其它URL参数,并把分解所得的这些结果存入request对象。

router使用很简单的方法来决定使用的controller及其action:

				http://framework.zend.com/controller/action/
			

上面controller就是我们要采用的控制器,action就是我们要采用的action。

可选择地,参数可以在URI中定义,并传递给controller。格式为key/value :

				http://framework.zend.com/controller/action/key1/value1/
			

如果URL中controller或action/这部份没有写,Zend_Controller_Dispatcher会尝试从request对象的参数中获取相应的值,如果没有找到,则使用默认值。不论controller还是action,默认都是调用index。如:

				http://framework.zend.com/roadmap/future/
				Controller: roadmap
				Action    : future

				http://framework.zend.com/roadmap/
				Controller: roadmap
				Action    : index

				http://framework.zend.com/
				Controller: index
				Action    : index
				

你也可以通过几种方式把controller放在子目录或模块下:

  • 使用下划线_来命名控制器,例如:http://framework.zend.com/admin_roadmap/future 将映射到Admin_RoadmapController控制器。如果你不想使用下划线,也可以使用其它分隔符号,通过dispatcher的setPathSeparator()方法可以设置。

  • 通过设置front controller的useModules参数,你可以使用干净漂亮的URI来访问子目录下的控制器。上例将访问http://framework.zend.com/admin/roadmap/future。要达到这个目的,可以在front controller或者router中设置useModules参数:

    				$front->setParam('useModules', true);
    				// or
    				$router->setParam('useModules', true);
    				

    这样的设置对于基本的router和RewriteRouter都有效。

[注意] Flexibility 灵活性

如果你想得到更多灵活性,你可以看看这里: 第 7.4.3 节 “Zend_Controller_Router_Rewrite”

controller和其中的action名称,及任何参数都在request对象中设置。当Zend_Controller_Front进行dispatch循环时,request对象将被传递给 Zend_Controller_Dispatcher

7.1.4. Dispatch Process 分配过程

“分配”(Dispatching,是指分发请求到具体的控制器的过程,也是调用控制器和方法的过程,从这个角度说理解为“调度”更为准确 --Haohappy注)是根据request对象(Zend_Controller_Request_Abstract),从中得到controller和action名称及参数,然后实例化一个controller并调用其中方法的过程。如果没有发现controller和action的名称,它会使用默认值。 Zend_Controller_Dispatcher指定index作为默认值,但允许开发者自行指定默认值,可以通过setDefaultController()setDefaultAction()方法。

调度过程发生于front controller内部的一个循环中。在调度发生之前,前端控制器把请求分解,得到控制器、方法的名称及参数,然后进行一个调度循环,分派请求。

在每次迭代的开始,request对象中都会有个标识变量(flag)来指示当前action是否已经被分配。如果一个action或者pre/postDispatch(分配前/后)插件清空了该标识变量,则分配过程会继续下去,尝试再次分配该请求。通过改变请求中的controller和/或action,或重设flag,程序员可以定义一定的“请求链”并执行。

The action controller method that controlls such dispatching is _forward(); call this method from any of the pre/postDispatch() or action methods, providing a controller, action, and optionally any additional parameters you may wish to send to the new action: 控制分配过程的类方法是 _forward(),在任何 pre/postDispatch()方法或者action方法中调用该方法,即可调用另一个action:

			public function myAction()
			{
			  // 进行一些处理...    
			  //跳转到另一个action,FooController::barAction():
			  $this->_forward('foo', 'bar', array('baz' => 'bogus'));
			}
			

7.1.5. Response Object 响应对象

response对象与request对象从逻辑上说是相对应的,它的目的是收集服务器返回的内容的header和body。另外,front controller将把捕捉到的异常传递到response对象中,允许程序员优雅地处理异常。这个功能可以通过设设置 Zend_Controller_Front::throwExceptions(true)来实现:

$front->throwExceptions(true);

由于response对象使用了__toString()方法,所以你可以很安全地用echo来输出该对象。也就是这样使用:

echo $controller->getResponse();

// or
$response = $controller->getResponse();
echo $response;

程序员应当在controller中使用response对的是,不要直接输出内容或头部,应该把这些输出放到response对象中去:

// Within an action controller action:
// Set a header
$this->getResponse()
    ->setHeader('Content-Type', 'text/html')
    ->appendBody($content);

这样做,所有的header会在发送内容之前设置一次。

程序中是否发生异常,可以使用isException()来检查response对象的flag,并且通过getException()来得到该异常。另外,你也可以定制自己的response对象,使程序出错时转向到出错信息页、记录异常信息或格式化异常信息等。

在front controller进行dispatch()之后,你可以得到response对象,或者请求front controller返回response对象而不是直接显示输出。

// retrieve post-dispatch:
//得到dispatch后的响应:
$front->dispatch();
$response = $front->getResponse();
if ($response->isException()) {
    // log, mail, etc...
}

// Or, have the front controller dispatch() process return it
// 或者,让front controller在dispatch()执行过程中返回响应
$front->returnResponse(true);
$response = $front->dispatch();

// do some processing...

// finally, echo the response
//最后,用echo输出响应信息
echo $response;

默认地,异常信息不会被显示。要显示异常信息,需要通过调用renderExceptions(),或者启用front controller的throwExceptions()方法,例如:

$response->renderExceptions(true);
$front->dispatch($request, $response);

// 或者
$front->returnResponse(true);
$response = $front->dispatch();
$response->renderExceptions();
echo $response;

// 或者
$front->throwExceptions(true);
$front->dispatch();