目录
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的分配状态被清零),循环会一直继续直到另一次分配执行完毕。
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对象,精细和准确的单元测试将变成可能。
在你构建第一个控制器之前,你需要理解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
。
“分配”(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')); }
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();