Zend_Controller架构
本文内容
- Zend_Controller工作流程
- 路由器 Zend_Controller_Router
- 分发器 Zend_Controller_Dispatcher
- 动作控制器 Zend_Controller_Action
- 插件 Zend_Controller_Plugin
- 总结
在 该系列的第一部分中,简要介绍了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方法:
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中声明的。
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)中。
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方法中有些什么:
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') && !emptyempty($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 (emptyempty($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 (emptyempty($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子系统,所以还会有一些操作。
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')) { nbsp; 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方法:
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工作流的细节后,可以通过所提供的可控性,来开发更高效的应用。
原文地址:http://gonefish.info/blog/?page_id=473