zoukankan      html  css  js  c++  java
  • Zend_Controller架构

    本文内容

      在 该系列的第一部分中,简要介绍了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工作流程如 下图:

    ZF1

      在 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

     

  • 相关阅读:
    服务器常用端口
    xml处理类
    水印的代码
    Asp.net常用的51个代码(非常实用)
    poj 2453
    MOD
    LIS(最长上升子序列)
    POJ各题算法分类(转)
    poj 1496&1850
    poj 1423
  • 原文地址:https://www.cnblogs.com/hongfei/p/2697745.html
Copyright © 2011-2022 走看看