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工作流的细节后,可以通过所提供的可控性,来开发更高效的应用。

 

posted @ 2009-07-13 22:13  villion  阅读(1158)  评论(0编辑  收藏  举报