Zend Framework工作流程
Zend Framework (ZF) 是用 PHP 5 来开发 web 程序和服务的开源框架。ZF 用 100% 面向对象编码实现。 ZF 的组件结构独一无二,每个组件几乎不依靠其他组件。这样的松耦合结构可以让开发者独立使用组件。 我们常称此为 “use-at-will”设计。
Zend Framework核心在于C层,M和V层已经被批得体无完肤,在此就不细说了。
本文主要介绍了Zend_Controller的相关组件,在第二部分的我们将详细介绍相关组件是怎样工作的,并结合源代码进行一些讲解。讲解源代码的版本是Zend Framework 1.7,但由于Zend_Controller已经相当的稳定,本文的讲解可以适用于1.X分支,如发现有差别,可查阅官方的从以前的版本移植章节。如不特殊说明,本文所有内容仅针对HTTP。 Zend_Controller的工作流程涉及到四个相关的组件:Zend_Controller_Router、 Zend_Controller_Dispatcher、Zend_Controller_Action、 Zend_Controller_Plugin;Zend_Controller_Request包装了请求的信息,在Zend_Controller的工作流程中,时刻与四个相关组件保存着联系;Zend_Controller_Response则是对输出进行包装,在Zend_Controller的工作流结束时被输出。
Zend_Controller工作流程
Zend_Controller 的工作流由前端控制器Zend_Controller_Front启动,Zend_Controller_Front除了启动工作流外,还包括处理在工作流中的相关设置,比如:路由器设置、分发器设置、插件操作、请求和响应对象等。当然在一般情况下,不用自己手动设置这些东西,系统会自动使用默认值。而且,在不明白Zend_Controller的工作流程情况下最好不要盲目设置。Zend_Controller工作流程如下图:
在 Zend_Controller_Front运行了dispatch方法后,Zend_Controller的工作流程开始运作。在 Zend_Controller_Front的dispatch方法中,包括经过路由器、由分发器调用控制器、控制器处理动作、运行插件机制等。在本文后面的部分将会分别对其进行介绍。
路由器 Zend_Controller_Router
路由器是Zend_Controller工作流中第一个遇到的组件。路由器的作用是分析当前请求的URI,根据路由规则解析出当前的URI需要调用的模块名、控制器名、控制器动作名、参数。这些信息会被传递给Zend_Controller_Request(本文中是 Zend_Controller_Request_Http)对象中。在路由器组件中包括两部分:路由器和路由规则,路由器用管理路由规则,而路由规则是用来解析当前的URI。路由器的实现需要继承Zend_Controller_Router_Abstract抽象类;而路由规则的实现则需要继承 Zend_Controller_Router_Route_Abstract抽象类,还必须实现 Zend_Controller_Router_Route_Interface中的方法。在Zend_Controller_Router中路由器默认的实现是Zend_Controller_Router_Rewrite,而路由规则的实现则有很多种。在路由器默认的实现中,核心处理位于 Zend_Controller_Router_Rewrite的route方法:
view plainprint?
1. public function route(Zend_Controller_Request_Abstract $request)
2. {
3.
4. if (!$request instanceof Zend_Controller_Request_Http) {
5. require_once 'Zend/Controller/Router/Exception.php';
6. throw new Zend_Controller_Router_Exception('Zend_Controller_Router_Rewrite requires a Zend_Controller_Request_Http-based request object');
7. }
8.
9. if ($this->_useDefaultRoutes) {
10. $this->addDefaultRoutes();
11. }
12.
13. /** Find the matching route */
14. foreach (array_reverse($this->_routes) as $name => $route) {
15.
16. // TODO: Should be an interface method. Hack for 1.0 BC
17. if (!method_exists($route, 'getVersion') || $route->getVersion() == 1) {
18. $match = $request->getPathInfo();
19. } else {
20. $match = $request;
21. }
22.
23. if ($params = $route->match($match)) {
24. $this->_setRequestParams($request, $params);
25. $this->_currentRoute = $name;
26. break;
27. }
28. }
29.
30. return $request;
31.
32. }
public function route(Zend_Controller_Request_Abstract $request)
{
if (!$request instanceof Zend_Controller_Request_Http) {
require_once 'Zend/Controller/Router/Exception.php';
throw new Zend_Controller_Router_Exception('Zend_Controller_Router_Rewrite requires a Zend_Controller_Request_Http-based request object');
}
if ($this->_useDefaultRoutes) {
$this->addDefaultRoutes();
}
/** Find the matching route */
foreach (array_reverse($this->_routes) as $name => $route) {
// TODO: Should be an interface method. Hack for 1.0 BC
if (!method_exists($route, 'getVersion') || $route->getVersion() == 1) {
$match = $request->getPathInfo();
} else {
$match = $request;
}
if ($params = $route->match($match)) {
$this->_setRequestParams($request, $params);
$this->_currentRoute = $name;
break;
}
}
return $request;
}
在这个执行过程上,会默认添加一个Zend_Controller_Router_Route_Module的路由规则,通过 addDefaultRoutes添加,以确保能使用默认路由规则,类似于[module/]controller/action/var1 /value1/var2/value2这种规则。这里还有一点是需要明确的,当在Zend_Controller_Front中只设置了一个控制器目录时,这个控制器的目录就是default模块的目录,而default模块的模块名称是不用在URI中声明的。
view plainprint?
1. public function addDefaultRoutes()
2. {
3. if (!$this->hasRoute('default')) {
4.
5. $dispatcher = $this->getFrontController()->getDispatcher();
6. $request = $this->getFrontController()->getRequest();
7.
8. require_once 'Zend/Controller/Router/Route/Module.php';
9. $compat = new Zend_Controller_Router_Route_Module(array(), $dispatcher, $request);
10.
11. $this->_routes = array_merge(array('default' => $compat), $this->_routes);
12. }
13. }
public function addDefaultRoutes()
{
if (!$this->hasRoute('default')) {
$dispatcher = $this->getFrontController()->getDispatcher();
$request = $this->getFrontController()->getRequest();
require_once 'Zend/Controller/Router/Route/Module.php';
$compat = new Zend_Controller_Router_Route_Module(array(), $dispatcher, $request);
$this->_routes = array_merge(array('default' => $compat), $this->_routes);
}
}
在添加默认路由规则的时候,确保了名为default的路由规则位于数组的第一位。在route方法中,反转了路由规则的数组,以确保名为default 的路由规则位于最后,以确保不会对其它的路由规则造成冲突。然后开始遍历所有的路由规则,以找到匹配的路由规则并解析出模块名、控制器名、控制器动作名、参数。并将这些信息设置到Zend_Controller_Request(本文中是Zend_Controller_Request_Http)中。
view plainprint?
1. protected function _setRequestParams($request, $params)
2. {
3. foreach ($params as $param => $value) {
4.
5. $request->setParam($param, $value);
6.
7. if ($param === $request->getModuleKey()) {
8. $request->setModuleName($value);
9. }
10. if ($param === $request->getControllerKey()) {
11. $request->setControllerName($value);
12. }
13. if ($param === $request->getActionKey()) {
14. $request->setActionName($value);
15. }
16.
17. }
18. }
protected function _setRequestParams($request, $params)
{
foreach ($params as $param => $value) {
$request->setParam($param, $value);
if ($param === $request->getModuleKey()) {
$request->setModuleName($value);
}
if ($param === $request->getControllerKey()) {
$request->setControllerName($value);
}
if ($param === $request->getActionKey()) {
$request->setActionName($value);
}
}
}
这样Zend_Controller工作流的第一个主要工作就结束了,之后的事情就交给分发器处理了。路由器的工作仅仅是对输入进行了解析,然后将模块名、控制器名、控制器动作名、参数信息,传递到请求对象,当然这些也可以对应到非HTTP的应用上。
分发器 Zend_Controller_Dispatcher
分发器是Zend_Controller工作流中第二个组件。分发器的所做的工作就简单多了,由于在经过路由器后,已经知道了当前请求所需要访问的模块名、控制器名、控制器动作名,所以Zend_Controller_Dispatcher会自动加载控制器类,然后调用控制器类中的dispatch方法。所有的控制器必须是Zend_Controller_Action的子类,dispatch方法接收一个控制器动作名的参数。让我们来看看 Zend_Controller_Dispatcher的dispatch方法中有些什么:
view plainprint?
1. public function dispatch(Zend_Controller_Request_Abstract $request, Zend_Controller_Response_Abstract $response)
2. {
3. $this->setResponse($response);
4.
5. /**
6. * Get controller class
7. */
8. if (!$this->isDispatchable($request)) { //判断是否被分发过,因为分发可以是多次的
9. $controller = $request->getControllerName();
10. if (!$this->getParam('useDefaultControllerAlways') && !emptyempty($controller)) { //在设置不使用默认控制器和控制器为空的时候抛出异常
11. require_once 'Zend/Controller/Dispatcher/Exception.php';
12. throw new Zend_Controller_Dispatcher_Exception('Invalid controller specified (' . $request->getControllerName() . ')');
13. }
14.
15. $className = $this->getDefaultControllerClass($request);
16. } else {
17. $className = $this->getControllerClass($request);
18. if (!$className) {
19. $className = $this->getDefaultControllerClass($request);
20. }
21. }
22.
23. /**
24. * Load the controller class file
25. * 加载由路由器解析出的控制器类文件,这里的loadClass并非是Zend_Loader::loadClass方法。
26. * 在这里的loadClass中,会通过提供的模块信息发现最终的控制器类文件,并会验证加载的类是否正确。
27. */
28. $className = $this->loadClass($className);
29.
30. /**
31. * Instantiate controller with request, response, and invocation
32. * arguments; throw exception if it's not an action controller
33. * 对象验证
34. */
35. $controller = new $className($request, $this->getResponse(), $this->getParams());
36. if (!$controller instanceof Zend_Controller_Action) {
37. require_once 'Zend/Controller/Dispatcher/Exception.php';
38. throw new Zend_Controller_Dispatcher_Exception("Controller '$className' is not an instance of Zend_Controller_Action");
39. }
40.
41. /**
42. * Retrieve the action name
43. */
44. $action = $this->getActionMethod($request);
45.
46. /**
47. * Dispatch the method call
48. * 设置分发器的状态
49. */
50. $request->setDispatched(true);
51.
52. // by default, buffer output 默认情况下,所有的输出放进缓冲区,以确定在头信息输出前发送
53. $disableOb = $this->getParam('disableOutputBuffering');
54. $obLevel = ob_get_level();
55. if (emptyempty($disableOb)) {
56. ob_start();
57. }
58.
59. try {
60. $controller->dispatch($action); //调用Zend_Controller_Action对象中的dispatch方法
61. } catch (Exception $e) {
62. // Clean output buffer on error
63. $curObLevel = ob_get_level();
64. if ($curObLevel > $obLevel) {
65. do {
66. ob_get_clean();
67. $curObLevel = ob_get_level();
68. } while ($curObLevel > $obLevel);
69. }
70.
71. throw $e;
72. }
73.
74. if (emptyempty($disableOb)) {
75. $content = ob_get_clean();
76. $response->appendBody($content);
77. }
78.
79. // Destroy the page controller instance and reflection objects
80. $controller = null;
81. }
public function dispatch(Zend_Controller_Request_Abstract $request, Zend_Controller_Response_Abstract $response)
{
$this->setResponse($response);
/**
* Get controller class
*/
if (!$this->isDispatchable($request)) { //判断是否被分发过,因为分发可以是多次的
$controller = $request->getControllerName();
if (!$this->getParam('useDefaultControllerAlways') && !empty($controller)) { //在设置不使用默认控制器和控制器为空的时候抛出异常
require_once 'Zend/Controller/Dispatcher/Exception.php';
throw new Zend_Controller_Dispatcher_Exception('Invalid controller specified (' . $request->getControllerName() . ')');
}
$className = $this->getDefaultControllerClass($request);
} else {
$className = $this->getControllerClass($request);
if (!$className) {
$className = $this->getDefaultControllerClass($request);
}
}
/**
* Load the controller class file
* 加载由路由器解析出的控制器类文件,这里的loadClass并非是Zend_Loader::loadClass方法。
* 在这里的loadClass中,会通过提供的模块信息发现最终的控制器类文件,并会验证加载的类是否正确。
*/
$className = $this->loadClass($className);
/**
* Instantiate controller with request, response, and invocation
* arguments; throw exception if it's not an action controller
* 对象验证
*/
$controller = new $className($request, $this->getResponse(), $this->getParams());
if (!$controller instanceof Zend_Controller_Action) {
require_once 'Zend/Controller/Dispatcher/Exception.php';
throw new Zend_Controller_Dispatcher_Exception("Controller '$className' is not an instance of Zend_Controller_Action");
}
/**
* Retrieve the action name
*/
$action = $this->getActionMethod($request);
/**
* Dispatch the method call
* 设置分发器的状态
*/
$request->setDispatched(true);
// by default, buffer output 默认情况下,所有的输出放进缓冲区,以确定在头信息输出前发送
$disableOb = $this->getParam('disableOutputBuffering');
$obLevel = ob_get_level();
if (empty($disableOb)) {
ob_start();
}
try {
$controller->dispatch($action); //调用Zend_Controller_Action对象中的dispatch方法
} catch (Exception $e) {
// Clean output buffer on error
$curObLevel = ob_get_level();
if ($curObLevel > $obLevel) {
do {
ob_get_clean();
$curObLevel = ob_get_level();
} while ($curObLevel > $obLevel);
}
throw $e;
}
if (empty($disableOb)) {
$content = ob_get_clean();
$response->appendBody($content);
}
// Destroy the page controller instance and reflection objects
$controller = null;
}
分发器所做的事情就简单的多,加载由路由器解析出来的控制器类,然后调用控制器类的dispatch方法。
动作控制器 Zend_Controller_Action
在经过路由器和分发器之后,后面的工作就是调用控制器动作了。但由于Zend_Controller_Action有一个Zend_Controller_Action_Helper子系统,所以还会有一些操作。
view plainprint?
1. public function dispatch($action)
2. {
3. // Notify helpers of action preDispatch state
4. // 执行所有位于Zend_Controller_Action_Helper的helper堆中的preDispatch方法
5. $this->_helper->notifyPreDispatch();
6.
7. $this->preDispatch(); //调用控制器的preDispatch钩子
8. if ($this->getRequest()->isDispatched()) {
9. if (null === $this->_classMethods) {
10. $this->_classMethods = get_class_methods($this);
11. }
12.
13. // preDispatch() didn't change the action, so we can continue
14. if ($this->getInvokeArg('useCaseSensitiveActions') || in_array($action, $this->_classMethods)) {
15. if ($this->getInvokeArg('useCaseSensitiveActions')) {
16. nbsp; trigger_error('Using case sensitive actions without word separators is deprecated; please do not rely on this "feature"');
17. }
18. $this->$action(); //调用控制器动作
19. } else {
20. $this->__call($action, array());
21. }
22. $this->postDispatch(); //调用控制器的postDispatch钩子
23. }
24.
25. // whats actually important here is that this action controller is
26. // shutting down, regardless of dispatching; notify the helpers of this
27. // state
28. // 执行所有位于Zend_Controller_Action_Helper的helper堆中的postDispatch方法
29. $this->_helper->notifyPostDispatch();
30. }
public function dispatch($action)
{
// Notify helpers of action preDispatch state
// 执行所有位于Zend_Controller_Action_Helper的helper堆中的preDispatch方法
$this->_helper->notifyPreDispatch();
$this->preDispatch(); //调用控制器的preDispatch钩子
if ($this->getRequest()->isDispatched()) {
if (null === $this->_classMethods) {
$this->_classMethods = get_class_methods($this);
}
// preDispatch() didn't change the action, so we can continue
if ($this->getInvokeArg('useCaseSensitiveActions') || in_array($action, $this->_classMethods)) {
if ($this->getInvokeArg('useCaseSensitiveActions')) {
trigger_error('Using case sensitive actions without word separators is deprecated; please do not rely on this "feature"');
}
$this->$action(); //调用控制器动作
} else {
$this->__call($action, array());
}
$this->postDispatch(); //调用控制器的postDispatch钩子
}
// whats actually important here is that this action controller is
// shutting down, regardless of dispatching; notify the helpers of this
// state
// 执行所有位于Zend_Controller_Action_Helper的helper堆中的postDispatch方法
$this->_helper->notifyPostDispatch();
}
在 Zend_Controller_Action会对Zend_Controller_Action_Helper子系统进行处理,还有自身的 preDispatch、postDispatch钩子操作。这里有一点非常值得注意的地方,就是在这个处理过程中手动调用了__call方法,如果由系统自动调用__call方法的话,非常的慢,我原来做过一个测试,通过自动调用要比显示调用慢一半左右。
Zend_Controller_Action_Helper_ViewRenderer 助手是默认加载的,该助手用于自动调用Zend_View 的render操作。而实现的机制就是使用了Zend_Controller_Action_Helper的postDispatch方法:
view plainprint?
1. public function postDispatch()
2. {
3. if ($this->_shouldRender()) {
4. $this->render();
5. }
6. }
public function postDispatch()
{
if ($this->_shouldRender()) {
$this->render();
}
}
在Zend_Controller_Action系统中还有自身的preDispatch、postDispatch、init方法,这些都提供了更多的途径来干扰控制器的执行过程。
插件 Zend_Controller_Plugin
在Zend_Controller的工作流过程中,在每一个阶段都会执行一些方法,总共分为6个阶段,在流程图中黄色的部分。这些方法通过Zend_Controller_Plugin插件机制来完成,关于插件机制的使用将会用该系列文章的第三部分进行说明。
总结
Zend_Controller的架构是非常优秀的,在Zend_Controller的工作流中,各方面的工作被细化,达到极为可控的层度。了解Zend_Controller工作流的细节后,可以通过所提供的可控性,来开发更高效的应用。