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

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值