Zend Framework中的MVC架构 1

本文详细介绍了 Zend Framework 中的 MVC 架构,包括请求处理流程、前端控制器的使用及配置、路由器的工作原理和路由协议的应用等核心内容。

Zend Framework中的MVC架构


The Zend Framework MVC Architecture
一、概述:
In this chapter, we will cover the following topics:
1. Zend framework MVC overview
2. The Front Controller
3. The router
4. The dispatcher
5. The Request object
6. The Response object

二、详细介绍:
1、Zend Framework MVC overview
  1)了解请求(REQUEST)的产生与处理过程

一个请求被产生,一个相应的响应就被返回。上面这个流程就是发生在前端控制器(Front Controller)内部,这个过程常常是在前端控制器(Front Controller)调用dispatch()方法是触发的,这个过程可以分解为下列12个小步骤:
  1. 一个请求Request的产生(创建了一个Request Object 对象);
  2. 路由事件routeStartup 触发;
  3. 路由器Route r开始处理这个请求,从中获取 请求信息;
  4. 路由时间 routeShutdown 触发,路由过程结束;

  5. 派遣事件dispatchLoopStartup 被触发;
    //派遣循环开始
    6. 派遣preDispatch 事件触发;
    7. 派遣过程中调用动作控制器(Action Controller );
    8. 动作控制器(Action Controller )将处理完成信息直接写入响应对象(Response Object );
    9.派遣post Dispatch 时间被触发;
    
//派遣循环结束
    10. 检测派遣标志,即检查是否还有动作没有完成,如果有再次进入派遣循环(第6步);
  11. 派遣事件dispatchLoopShutdown 被触发;
  12. 产生的响应Response被返回。

2、 The Front Controller--------前端控制器
   1)介绍:前端控制器是MVC组建中的苦力,因为它要实例 化对象、触发事件、建立默认的行为等,它的主要目的是处理所有进入应用 的请求。前端控制器的设计 模式被应用于不同的MVC框架 中, 我们在Zend Framework中指代的前端控制器(Front Controller)实际上是指Zend_Controller_Front类,因为该类实现了前端控制器的模式;另一定注意的是,前端控制器设计是单 例模式(Singleton),这也就意味着它实现了单例设计模式,也就是仅仅只能有一个实例化的前端控制器,即我们不能直接实例化Front Controller,而是拿取一个:

  1. $front = Zend_Controller_Front::get Instance();
复制代码



     2)默认情况下,Front Controller负责实例化很多对象,并且是针对WEB应用的,即这些对象都是默认指定在HTTP环境下被实例化出来的,例如下表:

这个表显示出了创建对象的类型,抽象类abstract class是被用于实体类concrete class继承,实体类是被前端控制器调用并实例化使用的!!插件经纪人有些特别因为它与运行环境无关,即在HTTP环境下和在CLI环境下是一样的。默 认情况下前端控制器有2个插件可用:
a.Zend_Controller_Plugin_ErrorHandler;
b.Zend_Controller_Plugin_ActionStack.
错误插件ErrorHandler默认是被注册的,可以通过前端控制器设置 其参数noErrorHandler来取消:

  1. $front->setParam('noErrorHandler',true);
复制代码


Stack index是用于插件的调用时机,它越大这个插件就将在越后面执行!

默认情况下,前端控制器Front Controller也利用动作助手经济人(Action Helper Broker)注册了ViewRenderer Action Helper,我们可以通过noViewRenderer参数来禁止它:

  1. $front->setparam('noViewRenderer',true);
复制代码


          3)使用前端控制器
      [A]调用参数
        调用参数可以被用于存储数据 在前端控制器中,然后被传递到Action Controller、Router、Dispatcher中去了,调用参数可以很好的实现将共同的对象或者变量 传递到MVC组件中去:

  1. //In bootstrap:
  2. $obj = new MyClass();
  3. $front->setParam('myObj',$obj);
复制代码
  1. //We can then retrieve this from one of our controllers using the getInvokeArg() method:
  2. $myObj = $this->getInvokeArg('myObj');
复制代码


前端控制器有如下处理调用参数的方法:

  • setParam(String $name, Mixed $value): 设置调用参数
  • setParams(Array $params): 设置多个调用参数
  • getParam(String $name): 获取一个调用参数
  • getParams(): 获取所有的调用参数
  • clearParams(String|Array|Null $name): 清除调用参数

      [B]可选项
        像调用参数样,可选项也可以影响前端控制器的默认行为,如下列可选项:

  • throwExceptions(Boolean $flag): 在派遣循环过程中是否抛出异常,或者捕获到Response对象中
  • setBaseUrl(String $base): 设置请求的基本路径,不要用全部Url。
  • returnResponse(Boolean $flag): 默认情况下,一旦派遣循环结束,前端控制器将渲染Response对象;若设为true,dispatch()方法将返回Response对象,而不是渲染它;
  • setDefaultControllerName(String $controller): 设置默认的控制器名;默认情况下Index是默认的控制器名,我们可以通过该方法改变它;
  • setDefaultAction(String $action): 设置默认的动作名;默认情况下index是默认动作名称,我们可以通过该方法来改变它;
  • setDefaultModule(String $module): 设置默认的模块名;默认情况下default是默认的模块名;
  • setModuleControllerDirectoryName(String $name);设置控制器路径名;默认下是controllers;
  1. $front = Zend_Front_Controller::getInstance();
  2. $front->setDefaultModule('eesmart')
  3. $front->setModuleControllerDirectoryName('c');
  4. $front->throwExceptions(ture);
  5. $front->setBaseUrl('/eesmart');
  6. $front->returnResponse(ture);
  7. $front->setDefaultControllerName('Eesmart');
  8. $front->setDefaultAction('eesmart');
复制代码


     4)Modules, controllers, and actions
       前端控制器一个主要的部分就是负责modules、controllers、actions的配置,为了工作正常,前端控制器必须知道如何组织我们的控制器和将它们放在哪里;
  默认情况下,我们利用Zend_Tool创建的目录 结构大致如下:
  
上面这种目录结构不利于管理,因为所有的控制器就在application/controllers一个控制器文件 夹下,这样不方便管理,为了达到这点,我们引入modules的概念,这样可以将controllers,models,views进入一个管理单元!!
所有我们引申出来了下面两种目录结构:
(图1)                           (图2)
                             
正如所见一样,图1和图2的布局基本一样,但图2的布局使用了module文件夹,这样做是为了告诉前端控制器这个是我们的modules文件路径,同时 我们也不必一定要采用上面的这两种模式,它们都可以自定义,然而99%的我们都会采用上面两种情况,而我最喜欢的还是图2中的路径布局。
★★
如果我们采用图一中的布局方式来使它能够使用前端控制器Front Controller,我们可以使用Front Controller的setControllerDirectory()或者是addControllerDirectory()方法,因此当我们应用 第一种布局方式(图1所示)时我们可以这样做:

  1. $front->setControllerDirectory(
  2.   array('default'=>'/path/application/default'),
  3.       'product'=>'/path/application/product')
  4. );
  5. //或者
  6. $front->addControllerDirectory('/path/application/product','product');
复制代码


setControllerDirectory和addControllerDirectory这两种方法真正区别在于setControllerDirectory() 接受一个module数组并我们要指定默认的模块名,而addControllerDirectory() 一次仅仅只接受一个模块。不管怎么样,我们一次必须要一次指定一个默认的模块名,无论我们是采用setControllerDirectory()还是addControllerDirecoty()方法!!
★★★★★
我们第二中布局(图2所示)只有一点点不同,所有的modules都被放入一个叫做modules的路径下,我们可以采用addModuleDirectory() 方法来让这种布局使用Front Controller控制器,因此我当我采用第二种布局方式(图2所示)时我们可以这样做:

  1. $front->addModuleDirectory('/path/application/modules');
复制代码


这种方法是最简单的增加多模块的方法,同时也意味着你不需要从新定义你新添加的modules。
★★★★★
当我们使用了模块后,我们在非默认模块的的动作控制器中需要遵循一个防止命名控制冲突的不同的命名协议。因此,在我们的例子中,所有在product模块 中的动作控制器Action Controllers都需要增加一个Product的命名空间,例如,Product模块中的detail的动作控制器应当命名成 Product_DetailsController,但对于默认模块(default(global) modules)中的动作控制器则不需要,例如,default模块中的detail的动作控制器应该命名成DetailsController,然而, 我们通过设置prefixDefaultModule 变量能够改变这种默认的行为:

  1. $front->setParam('prefixDefaultModule',true);
复制代码


通过这样,我们的default模块中的所有动作控制器(如detail动作控制器)都应该加上模块前缀,即Default_(Default_DetailsController);

     5)自定义MVC组件
  大多数情况下,默认的MVC组件(指Requset、Response、Router、Dispatcher等)基本上可以很好的服务于我们的应用程序 ,但是如果我们真的需要自定义其中的一个或者几个话,前端控制器(Front Controller)提供给我们一些很简单的方法就可以定义出我们自定义的MVC组件,例如:

  1. //通过注册前端控制器而非实例化得到前端控制器
  2. $front = Zend_Controller_Front::getInstance();
  3. //实例化我们自己已经定义好的MVC组件
  4. $myRequest = new MyRequest();
  5. $myResponse = new MyResponse();
  6. $myRouter = new MyRouter();
  7. $myDispatcher = new MyDispatcher();
  8. //通过前端控制器来设置我们自定义的MVC组件让它们生效
  9. $front->setRequest($myRequest);
  10. $front->setResponse($myResponse);
  11. $front->setRouter($myRouter);
  12. $front->setDispatcher($myDispatcher);
复制代码


在这里,我们用我们自定义的MVC组件来代替原有ZF默认的MVC组件。要做到这点,我们首先要通过继承MVC组件的抽象类或者是MVC组件的子类(这个 在我们上面的一个表中可以很清晰的看到)来定义我们自己的MVC组件,这样再通过上面的代码就可以设置好我们自己的MVC组件。在大多数的案例中,你可能 只需要自定义其中一两个MVC组件就行了,例如有可能用到自定义路由!
  
           6)小结
    前端控制器用一个集中的地方提供给我们控制我们的MVC组件,回顾下前端控制器的主要方法和函数 ,我们所提到的这些方法和函数是前端控制器主要的一些方法和函数,在这里并没有列出所有的方法来,因为那样和手册 上基本又重复了一遍,所有没有必要。我建议你可以看下手册熟悉下前端控制器的基本方法,如果你想了解ZF的前端控制器的每一个过程,你可以看看zf原代码。

3、Router-----------路由器
  1)概述:路由器主要负责解析一个请求并且决定什么module、controller、action被请求;它同时也定义了一种方法来实现用户 自定义路由,这也使得它成为最有用的一个MVC组组件;
  
      2)设计:作为一个应用中的路由组件是很专业的,理所当然的路由组件是抽象的,这样允许作为开发 者的我们很容易的设计出我们自定义的路由协议。然而,默认的路由组件其实已经服务得我们很好了。记住,如果我们需要一个非标准的路由协议时候,我们就可以自定义一个自己的路由协议,而不用采用默认的路由协议。
  事实上,路由组件有两个部分:路由器(或者称路由对象《the router》)和路由过程(或者称路由协议《the route》)。路由器主要负责管理和运行路由链,路由过程事实上主要负责匹配我们预先定义好的路由协议,意思就是我们只有一个路由器,但我们可以有许多 路由协议。(ps:不知道这样理解是不是有问题,原文是这样的:The router actually has two parts, the router and the route. The router is responsible for managing and running the route chain, and a route is responsible for actually matching the request against the predefined rule of the route. This means that we have one router to many routes.看到后面就会清楚一点的。)
  路由组件基于两个接口:Zend_Controller_Router_Interface 和 Zend_Controller_Router_Route_Interface;同样路由组件的两个抽象 类:Zend_Controller_Router_Abstact和Zend_Controller_Router_Route_Abstract分别 是实现了上面对应的两个接口,同时这两个抽象类也就提供给我们一些基本的函数来操作路由组件。如果我们需要创建我们自定义的路由器(the router)或者路由协议(the route),我们就可以分别继承上面的两个抽象类。路由的过程发生派遣过程的最开始,并且路由解析仅仅发生一次。路由协议在任何控制器动作 (controller action)被派遣之前被解析,一旦路由协议被解析后,路由器将会把解析出得到的信息传递给请求对象(Request object),这些信息包括moduel、controller、action、用户params等。然后派遣器(Dispatcher)就会按照这些 信息派遣正确的控制器动作。路由器也有两个前端控制器插件钩子,就是在我们之前提到过的routeStartup和routeShutdown,他们在路 由解析前后分别被调用。
  
      3)默认情况下:
   默认条件下,我们的路由器是使用Zend_Controller_Router_Rewrite,是基于HTTP路由的,意味着它期望一个请求是 HTTP请求并且请求对象是使用Zend_Controller_Request_Http(或者是继承它的对象),这两个默认类,在我们之前的那个表中 都有见到过。默认条件下,路由协议是使用Zend_Controller_Router_Route_Module类。
  
     4)使用路由:
  使用路由既可以让之很复杂,同时也能让它很简单,这是归于你的应用。然而使用一个路由是很简单的,你可以创建一个路由器让它管你的路由协议,然后你可以添加你的路由协议给路由器,这样就OK了!
  不同的路由协议如下所示:

  • Zend_Controller_Router_Route
  • Zend_Controller_Router_Route_Static
  • Zend_Controller_Router_Route_Regex
  • Zend_Controller_Router_Route_Hostname
  • Zend_Controller_Router_Route_Chain
  • Default Routes

  其中我们使用到的最基本的路由协议类数Zend_Controller_Router_Route,它提供给们少量的控制。如果想要更精密的控制,我们可以采用正则 路由协议:Zend_Controller_Router_Route_Regex,它提供给我们可以通过PHP 的正则来使用,很强大。其他的几个路由协议类分别有不同的专业化。到此为止,首先让我们来看看路由器是如何让路由协议与之一起工作的。
  在我们添加任何路由协议之前我们必须要得到一个路由器(the router)实例,我们可以通过自己创建一个新的路由器或者是通过前端控制器(Front Controller)来得到一个默认的路由器:

  1. //我们实例化一个默认的路由器
  2. $router = new Zend_Controller_Router_Rewrite();
  3. //或者我们可以通过前端控制器的getRouter()方法得到一个默认的路由器实例
  4. $router = $front->getRouter()
复制代码


  一旦我们有了路由器实例,我们就能通过它来添加我们自定义的一些路由协议:

  1. $router->addRoute('myRoute',$route);
  2. $router->addRoute('myRoute1',$route)
复制代码


  除此之外,我们还可以通过Zend_Config_Ini和Zend_Config_Xml对象来添加我们的路由协议:

  1. $config = new Zend_Config_Ini('/path/to/config.ini', 'production');
  2. $router->addConfig($config, 'routes');
复制代码


  其实路由器也提供给我们不同的方法来得到和设置包含在它内部的信息,一些重要的方法如下:

  • addDefaultRoutes() and removeDefaultRoutes()//添加或者移除默认的路由协议。
  • assemble()//基于给定的路由协议确定URI,这个方法通过Url视图助手(View Helper)使用提供它的链接地址
  • getCurrentRoute() and getCurrentRouteName()
  • getRoute(), getRoutes(), hasRoute(), and removeRoute();//看函数基本意思也就知道.

    5)路由协议详解:
    【A】Zend_Controller_Router_Route
    Zend_Controlloer_Router_Route路由协议提供了我们很强的功能 ,同时也提供了一些简单的操作,为了能够使用该路由协议,我们必须先实例化它,然后用路由器加载它:

  1. //创建一个路由器实例
  2. $router = new Zend_Controller_Router_Rewrite();
  3. //创建一个路由协议实例
  4. $route = new Zend_Controller_Router_Route(
  5.   'product/:ident',
  6.   array(
  7.     'controller' => 'products',
  8.     'action' => 'view'
  9.   )
  10. );
  11. //使用路由器装载路由协议
  12. $router->addRoute('product', $route);
复制代码


在这个例子中,我们试图匹配Url指定到一个单一的产品,就像http://domain.com/product/choclolat-bar 。 为了实现这点,我们在路由协议中传递了2个变量到路由协议Zend_Controller_Router_Route的构造其中。第一个变量 ('product/:indent')就是匹配的路径,第二个变量(array变量)是路由到的动作控制器;其实路由协议也提供了第三个变量用于正则匹 配,我们将在第二个路由协议中见到;
路径使用一个特别的标识来告诉路由协议如何匹配到路径中的每一个段,这个标识有有两种,可以帮助我们创建我们的路由协议,如下所示:
  a) :
  b) *
冒号(:) 指定了一个段,这个段包含一个变量用于传递到我们动作控制器中的变量,我们要设置好事先的变量名,比如在上面我们的变量名就是'ident',因此,倘若我们访问http://domian.com/product/chocoloate-bar 将会创建一个变量名为ident并且其值是'chocoloate-bar'的变量,我们然后就可以在我们的动作控制器ProductsController/viewAction下获取到它的值:$this->_ge tParam( ' ident ' );同时我们还可以在路由协议中设置ident的默认的值,即可以在路由协议类的第二个数组变量中增加一个元素(比如我们在这定义了ident默认值为unknown):

    • $route = new Zend_Controller_Router_Route(
    •   'product/:ident',
    •   array(
    •     'controller' => 'products',
    •     'action' => 'view',
    •     'ident' => 'unknown'
    •   )
    • );
复制代码


星号(*)被用做一个通配符,意思就是在Url中它后面的所有段都将作为一个通配数据被存储。例如,如果我们有路径'path/product/:ident/*'(就是路由协议中设置的第一个变量),并且我们访问的Url为http://domain.com/product/chocol ... lue1/another/value2,那么所有的在'chocolate-bar'后面的段都将被做成变量名/值对,因此这样会给我们下面的结果:
ident = chocolate-bar
test = value1
another = value2
这种行为也就是我们平常默认使用的路由协议的行为,记住变量名/值要成对出现,否则像/test/value1/这样的将不会这种另一个变量,我们有静态 的 路由协议部分,这些部分简单地被匹配来满足我们的路由协议,在我们的例子中,静态部分就是product;就像你现在看到的那样,我们的 Router_Route路由协议提供给我们极大的灵活性来控制我们的路由;然而,这就就很像正则匹配了,正则匹配使我们能够提供而外的约束力来限制我们 的路由(这里的正则匹配是使用PHP的preg引擎)。在我们的产品实例中,我们得到了用户想观看的'ident'的产品特性,即我们通过用户传递过来的 参数,通过数据库 的搜索得到正确的产品信息。然而,如果我们得到的需求是系统 仅仅只能接受产品ID号作为我们的产品的标识,那么我们可以使用路由协议来实现这点:
考虑下面两中路由:

  1. //创建路由器
  2. $router = new Zend_Controller_Router_Rewrite();
  3. //创建路由协议
  4. $route = new Zend_Controller_Router_Route(
  5.   'product/:ident',
  6.   array(
  7.     'controller' => 'products',
  8.     'action' => 'view'
  9.   ),
  10.   array(
  11.     // match only alpha, numbers and _-
  12.     'ident' => '[a-zA-Z-_0-9]+'
  13.   )
  14. );
  15. //让路由器装载路由协议
  16. $router->addRoute('productident', $route);
  17. //再定义一个路由协议
  18. $route = new Zend_Controller_Router_Route(
  19.   'product/:id',
  20.   array(
  21.     'controller' => 'products',
  22.     'action' => 'view'
  23.   ),
  24.   array(
  25.     // match only digits
  26.     'id' => '/d+'
  27.   )
  28. );
  29. //让我们的路由器再装载一个路由协议
  30. $router->addRoute('productid', $route);
复制代码

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值