Zend Framework 剖析之MVC
【开篇】在Web开发中,除了ASP.NET的Page Controller之外,MVC是其他开发语言中一个非常重要和常用的架构模式,本文就Zend Framework中的 MVC处理流程做一下浅显的分析。
【结构】
这里是一个Zend Framework 开发项目的目录结构,可以做为参考。具体的Front Controller 设计模式、MVC等可以参阅相关资料。目录结构如下:
/app/controllers 所有的controller
/app/models 所有的model
/app/views/scripts view的模板文件夹
/config 存放config文件
/db 数据库相关脚步、sql存放
/lib 库文件,如/lib/Zend存放Zend Framework,/lib/YourProj可以存放自己项目的库文件
/log 日志文件
/root 根目录,该文件夹下只有一个文件index.php即前端控制器,.htaccess是apache配置文件,将所有的请求转发到index.php。
一个典型的前端控制器代码:
$ctrl = Zend_Controller_Front::getInstance();
$ctrl ->throwExceptions(true);
$ctrl ->dispatch();
OK,一个dispatch就处理完了所有的请求,将处理结果传给了客户端,那么我们就从 Zend_Front_Controller来开始我们的Zend Framework之旅吧。
【流程分析】
首先,打开文件 /lib/Zend/Controller/Front.php文件。
getInstance 方法:
很简单的 Singleton 设计模式。下面进入构造函数。
__construct 方法:
$this->_plugins = new Zend_Controller_Plugin_Broker();
初始化当前 _plugins 字段。
在 实际开发中,本人觉得前端控制器的构造方法不应该是private,而应改成protected,理由很简单,如果要继承该Front Controller,那么就可以在构造函数中调用父类的构造函数,private的则没有办法实现,如果你要继承Front Controller的话,那必须在你的构造函数中加上一句: $this->_plugins = new Zend_Controller_Plugin_Broker();否则,当前的_plugins会因为没有初始化而出错。
构造完之后,我们来重点分析一下dispatch方法。
dispatch方法:
dispatch方法是整个Zend MVC处理过程的核心,由于代码比较多,这里采用注释加评说的方法来讲解。
dispatch主要包括以下几个阶段:
(1)初始化阶段
/**
* 判断当前参数中是否设定了noErrorHandler参数,如果有或者已经有
* Plugin_ErrorHandler就不加载 这个plugin,否则就加载这个Plugin
*/
if (!$this->getParam('noErrorHandler') && !$this->_plugins->hasPlugin('Zend_Controller_Plugin_ErrorHandler')) {
// Register with stack index of 100
$this->_plugins->registerPlugin(new Zend_Controller_Plugin_ErrorHandler(), 100
);
}
/**
*判断当前参数是否设定了noViewRenderer参数,如果有或者已经有了一个viewRenderer
*就不添加 Helper_ViewRenderer,否则就添加
*Zend_Controller_Action_Helper_ViewRenderer 实例作为默认ViewRenderer
*/
if (!$this->getParam('noViewRenderer') && !Zend_Controller_Action_HelperBroker::hasHelper('viewRenderer')) {
Zend_Controller_Action_HelperBroker::addHelper(new Zend_Controller_Action_Helper_ViewRenderer()
);
}
Zend Framework MVC中plugin机制类似于ASP.NET中的HTTPModule,也就是说你添加的所有的plugin都会在处理具体的Action之前和之后触 发相应的事件。一次请求只能有一个具体的action来处理(可以通过setDispatched的方法来让一个请求处理多个action),但是可以有多个plugin。这个和ASP.NET中的HTTPHandler和HTTPModule完全类似。 一个plugin必须继承Zend_Controller_Plugin_Abstract 类,重载某些方法来实现相应的功能。 主要的事件有:
public function preDispatch(Zend_Controller_Request_Abstract $request){} public function postDispatch(Zend_Controller_Request_Abstract $request){}
preDispatch是在调用具体action之前触发,所以在这个地方,我们可以添加权限认证,URL转发等动作。
postDispatch是在调用action之后触发,在这个地方,可以做输出缓存等。 如何注册一个plugin呢?前端控制器中有一个方法registerPlugin,所以最简单的办法就是在index.php中初始化一个 plugin,然后调用 $ctrl->registerPlugin(myPlugin);
/**
* 如果没有提供Request对象,则用默认的Request对象
*/
if (null !== $request) {
$this->setRequest($request);
} elseif ((null === $request)
&& (null === ($request = $this->getRequest()))) {
require_once 'Zend/Controller/Request/Http.php';
$request = new Zend_Controller_Request_Http();
$this->setRequest($request);
}
Response 对象的处理类似,在此略过。
/**
* 给所有注册的plugin添加request和repsonse对象
*/
$this->_plugins
->setRequest($this->_request)
->setResponse($this->_response);
/**
*初始化一个Router,默认的路由是用 Zend_Controller_Router_Rewrite
*/
$router = $this->getRouter();
$router->setParams($this->getParams());
/**
*初始化一个dispatcher,默认的dispatcher是
*Zend_Controller_Dispatcher_Standard
*/
$dispatcher = $this->getDispatcher();
$dispatcher->setParams($this->getParams())
->setResponse($this->_response);
如何去改变默认提供的这些路由、dispatcher以及其他参数对象呢?在Zend_Controller_Front中都有类似 setXYZ的方法,以供设置这些对象,当然也有 对应的getXYZ来获取这些对象。所以一个典型的前端控制器的初始化也通常有如下写法:
(2)路由阶段
/**
* 触发所有plugin的routeStartup事件
*/
$this->_plugins->routeStartup($this->_request);
/**
* 路由当前请求,给request对象添加一些重要参数:module、controller、action,
* 表示当前的请求由哪一个module的 那一个controller的那个action处理
*/
$router->route($this->_request);
/**
* 触发所有plugin的routeShutdown事件
*/
$this->_plugins->routeShutdown($this->_request);
/**
* 触发所有plugin的dispatchLoopStartup事件
*/
$this->_plugins->dispatchLoopStartup($this->_request);
(3)分发处理
/**
* 设置当前已经被dispatch
*/
$this->_request->setDispatched(true);
/**
* 触发所有plugin的preDispatch
*/
$this->_plugins->preDispatch($this->_request);
/**
* 最重要的方法,该方法处理了以下动作:从request中找到当前的module、controller、
* action,然后实例化相应的类,最后执行action方法
*/
$dispatcher->dispatch($this->_request, $this->_response);
/**
* 触发所有plugin的 postDispatch
*/
$this->_plugins->postDispatch($this->_request);
/**
* 触发所有plugin的 dispatchLoopShutdown
*/
$this->_plugins->dispatchLoopShutdown();
(4)收尾阶段
如果要返回response对象,那么就返回当前的Response,否则就将response对象中的内容直接输出。
可以在这里返回response对象,然后对response对象做一些自定义的处理。
if ($this->returnResponse()) {
return $this->_response;
}
$this->_response->sendResponse();
【参考】
Zend Framework 开发Jira地址: http://framework.zend.com/issues/secure/Dashboard.jspa
SVN仓库:http://framework.zend.com/svn/framework/trunk