Zend Framework 剖析之MVC

// December 23rd, 2009 // PHP知识累计, Zend Framework 累积

【开篇】
在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会因为没有初始化而出错。

?在实际开发中,本人觉得前端控制器的构造方法不应该是private,而应改成protected,理由很简单,如果要继承该Front Controller,那么就可以在构造函数中调用父类的构造函数,private的则没有办法实现,如果你要继承Front Controller的话,那必须在你的构造函数中加上一句:
$this->_plugins = new Zend_Controller_Plugin_Broker();否则,当前的_plugins会因为没有初始化而出错。

<!–[if !vml]–>
构造完之后,我们来重点分析一下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);

?Zend Framework MVC中plugin机制类似于ASP.NET中的HTTPModule,也就是说你添加的所有的plugin都会在处理具体的Action之前和之后触 发相应的事件。一次请求只能有一个具体的action来处理(可以通过setDispatched的方法来让一个请求处理多个action,我们将这个做 为一个小技巧 J ),但是可以有多个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);

<!–[if !vml]–>
/**
* 如果没有提供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())

?如何去改变默认提供的这些路由、dispatcher以及其他参数对象呢?在Zend_Controller_Front中都有类似 setXYZ的方法,以供设置这些对象,当然也有 对应的getXYZ来获取这些对象。所以一个典型的前端控制器的初始化也通常有如下写法:
$front->setRouter($router)
???? ??->setDispatcher(new Zend_Controller_ModuleDispatcher())
???? ??->registerPlugin(new My_Plugin_Auth($auth, $acl))
???? ??->registerPlugin(new My_Plugin_View($view))
??->setControllerDirectory(array(
”default” => realpath(”../app/controllers/default”),
”admin” => realpath(”../app/controllers/admin”)
))
???? ??->setParam(”auth”, $auth)
???? ??->setParam(”view”, $view)
???? ??->setParam(”config”, $config)
???? ??->setParam(”sitemap”, $sitemap)
???? ??->dispatch();???????????????? ???????????????????

<!–[if !vml]–>          ->setResponse($this->_response);
如何去改变默认提供的这些路由、dispatcher以及其他参数对象呢?在Zend_Controller_Front中都有类似 setXYZ的方法,以供设置这些对象,当然也有 对应的getXYZ来获取这些对象。所以一个典型的前端控制器的初始化也通常有如下写法:

$front   ->setRouter($router)
->setDispatcher(new Zend_Controller_ModuleDispatcher())
->registerPlugin(new My_Plugin_Auth($auth, $acl))
->registerPlugin(new My_Plugin_View($view))
->setControllerDirectory(
array( ”default” => realpath(”../app/controllers/default”),
&nbs

p;         ”admin” => realpath(”../app/controllers/admin”)
))
->setParam(”auth”, $auth)
->setParam(”view”, $view)
->setParam(”config”, $config)
->setParam(”sitemap”, $sitemap)
->dispatch();

(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_Controller_Action_HelperBroker 类实现了Helper类到Action类的代理,是Zend 构架中一个很优秀的机制,这种模式有点类似注册工厂,也就是向HelperBroker注册对象,然后再从HelperBroker中取回对象,下面是注 册工厂参考图

但是又不完全和注册工厂一样,在获取Helper类的时候是实例化了一个HelperBroker,又和MonoState模式做法一样,如果要和模式挂上钩的话,那么可以暂且称之为 “RegistryMonoState” 。
不足之处是PluginBroker类,并且在前端控制器的构造函数中实例化PluginBroker是非常不好的做法,PluginBroker也完全可以用HelperBroker类的做法,这样可以大大降低耦合性。

<!–[if !vml]–><!–[endif]–>

【参考】
Zend Framework 开发Jira地址: http://framework.zend.com/issues/secure/Dashboard.jspa
SVN仓库:http://framework.zend.com/svn/framework/trunk

http://www.diybl.com/course/4_webprogram/php/phpjs/20090307/159362.html

Leave a Reply

You must be logged in to post a comment.