Zend Framework中的MVC架构 2

为了达到我们的需求,我们创建了2种路由协议。第一种路由协议对Zend_Controller_Router_Route的构造函数添加了第三个变量--一个正则表达式 的变量ident,这个需求就是用户提供的ident必须是字母、数字和-以及下划线组成。我们的第二个路由协议试图匹配一个产品的ID数,我们利用/d+正则来匹配数字。通过我们增加的路由协议,如果我们现在浏览http://domain.com/product/12 ,这个id变量就会被设置,如果我们浏览http://domain.com/product/chocoloate-bar ,那么这个ident变量就会被设置,然后我们可以在同一个动作控制中接受不同的参数来显示同样的信息!!
   【B】Zend_Controller_Router_Route_Static
   标准路由协议,如果我们不需要任何匹配的变量,我们可以通过使用标准路由协议来实现。这个路由协议匹配到一个静态URL 并且创建一个静态的路由协议,我们仅仅需要像之前那样实例化它并把它加载到路由器中就行了:

  1. $route = new Zend_Controller_Router_Route_Static(
  2.   'products/rss',
  3.   array(
  4.     'controller' => 'feed',
  5.     'action' => 'rss'
  6.   )
  7. );
  8. $router->addRoute('rss', $route);
复制代码


就像你看到那样,Router_Route_Static路由协议就是Router_Route一个非常简单的版本,在我们的例子中,http://domain.com/products/rss 就会去访问FeedController和rss控制器;

   【C】Zend_Controller_Router_Route_Regex
    正则路由协议。到目前为止,我们之前的路由协议(Router_Route、Router_Route_Static)都很好的完成了基本的路由操作,我 们常用的也是他们,然而它们会有一些限制,这就是我们为什么要引进正则路由(Router_Route_Regex)的原因。正则路由给予我们preg 正则的全部力量,但同时也使得我们的路由协议变得更加复杂了一些。即使是他们有点复杂,我还是希望你能好好掌握它,因为它比标准路由协议(Router_Route)要快一点点。
    一开始,我们先对之前的产品案例改用使用正则路由:

  1. $route = new Zend_Controller_Router_Route_Regex(
  2.   'product/([a-zA-Z-_0-9]+)',
  3.     array(
  4.       'controller' => 'products',
  5.       'action' => 'view'
  6.   )
  7. );
  8. $router->addRoute('product', $route);
复制代码


你可以看到,我们现在移动我们的正则到我们的path(构造函数的第一个参数)中来了,就像之前的那样,这个正则路由协议现在应该是匹配是一个数字、字母、-和_组成的ident变量的字符 提供给我们,但是,你一定会问,ident变量在哪呢?好,如果你使用了这个正则路由协议,我们可以通过变量1 (one) 来获取其值,即可以在控制器里用:$this->_getParam(1)来获取,其实这里如果看过正则的都知道这就是反向引用中的/1。然而,你 一定会想为什么要定义的这么的垃圾,我们不能够记住或者弄清每一个数字代表的是什么变量(其实我刚开始看的时候也是一样的感受)。为了改变这点,正则路由 协议的构造函数提供了第3个参数来完成数字到变量名的映射:

  1. $route = new Zend_Controller_Router_Route_Regex(
  2.   'product/([a-zA-Z-_0-9]+)',
  3.   array(
  4.     'controller' => 'products',
  5.     'action' => 'view'
  6.   ),
  7.   array(
  8.     //完成数字到字符变量的映射
  9.     1 => 'ident'
  10.   )
  11. );
  12. $router->addRoute('product', $route);
复制代码


这样,我们就简单的将变量1映射到了ident变量名,这样就设置了ident变量,同时你也可以在控制器里面获取到它的值。(如果你不是很了解正则,我建议你可以看下那个正则入门30分钟... )
  另外,东西总是就两面性的,连正则路由也不例外。正则路由的一个负面作用就是表现在其他zf组件如url视图助手 ($this->baseUrl())不能够解析正则路由协议成URL,围绕这点,我们可以为我们的路由协议提供一个反向重写,就像 sprintf()工作的那样:
为了达到重写的目的,我们将正则路由协议(Router_Route_Regex)的构造函数中添加第四个变量:

  1. $route = new Zend_Controller_Router_Route_Regex(
  2.   'product/([a-zA-Z]+)/([a-zA-Z-_0-9]+)',
  3.   array(
  4.     'controller' => 'products',
  5.     'action' => 'view'
  6.   ),
  7.   array(
  8.     1 => 'category'
  9.     2 => 'ident'
  10.   ),
  11.   //重写路由协议
  12.   'product/%s/%s'
  13. );
  14. $router->addRoute('product', $route);
复制代码


那现在我们已经增加了一个反向重写(reverse rewrite),我们的路由协议能够很容易的被连接到。如果你看到上面的路由协议,我们实际上可以看作是一类参数的捕获。我们然后提供了反向重写 product/%s%s,因此路由协议能够为我们提供变量。记住,这里反向重写可以先熟悉下sprintf()这个函数。
由于我们感觉这个过程相当复杂,然我们再用一个实例来说明。
设想一下,假设我们一直在忙于我们老商城应用的重构而采用了zf框架。我们已经决定我们想让我们的产品的URl有一个好的印象针对于搜索引擎。然而由于我 们的产品已经开发完成了很久了,并且里面的url已经很多的被搜索引擎给收录了,我们不想失去这些链接,为了完成这些,我们正好可以使用正则路由的力量。
我们老的URL格式:
http://storefront/products.php/category/{categoryID}/product/{productID}
我们新的URL的格式:
http://storefront/product/{categoryName}/{productID}-{productIdent}.html
因此,一开始,我们就想重定义我们老的请求URL到我们新的请求,我们可以通过这样做:

  1. //我们老的url匹配的正则路由协议
  2. $route = new Zend_Controller_Router_Route_Regex(
  3.   'products.php/category/(/d+)/product/(/d+)',
  4.   array(
  5.     'controller' => 'products',
  6.     'action' => 'old'
  7.   ),
  8.   array(
  9.     1 => 'categoryID',
  10.     2 => 'productID'
  11.   )
  12. );
复制代码


在这里,我们将我们老的url中的category和product值分别得到后分别映射到了cateforyID和productID两个变量,并且将 这两个变量传递到我们的ProductsController/oldAction中去,因此,我们可以在我们的old动作中再次重定向到我们新的URl 中:

  1. public function oldAction(){
  2.   $catID = $this->_getParam( 'categoryID' );
  3.   $productID = $this->_getParam( 'productID' );
  4.   // model finds the product ident and category names
  5.   //....
  6.   $ident = 'coolproduct';
  7.   $catName = 'coolstuff';
  8.      //重定向到新的url
  9.   $this->_redirect( '/product/' . $catName . '/' . $productID . '-'. $ident . '.html',
  10.     array( 'code' => 301 )
  11.   );
  12. }
复制代码


这样我们old动作控制器拿取匹配的变量从路由协议中并且使用它们重定向到一个使用一个301的新的url中去,记住,我们不应当直接将我们获取到的变量 直接应用到我们的一个重定向请求中,因为这样会牵扯到安全问题。那现在我们创建一个新的路由协议来接收我们老的URls映射过来的URLs:

  1. //新url的正则路由协议
  2. $route = new Zend_Controller_Router_Route_Regex(
  3.   'product/([a-zA-Z-_0-9]+)/(/d+)-([a-zA-Z-_0-9]+).html',
  4.   array(
  5.     'controller' => 'products',
  6.     'action' => 'view'
  7.   ),
  8.   array(
  9.     1 => 'categoryIdent',
  10.     2 => 'productID',
  11.     3 => 'productIdent'
  12.   ),
  13.   'product/%s/%d-%s.html'
  14. );
复制代码


这个路由协议匹配我们新的URLs,这个正则包含3个捕获组,分别是产品类名(categoryIdent),产品ID(productID),以及产品特性(ident)。这个'product/%s/%d-%s.html'就是匹配原来的{categoryName}/{productID}-{productIdent}.html这种格式。 最后,建议好好玩玩这个正则路由协议,我可以保证你以后会用得到的。

【D】Zend_Controller_Router_Route_Hostname

    主机域名路由协议,看名字就知道他是关于处理域名的路由协议。一个常见使用就是一个域名下有按用户的子域名,如,如果我们有一个公共的外部站点www.domain.com ,现在我们的注册用户有一个帐号url像ues1.domain.com,那么我们就可以使用域名路由协议来重写这个请求:

  1. $route = new Zend_Controller_Router_Route_Hostname(
  2.   ':username.domain.com',
  3.   array(
  4.     'controller' => 'account',
  5.     'action' => 'index'
  6.   ),
  7.   array(
  8.     // Match subdomain excluding www.
  9.     'username' => '(?!.*www)[a-zA-Z-_0-9]+'
  10.   )
  11.   );
  12. $router->addRoute('account', $route);
复制代码


正如你所见,域名路由协议(Router_Route_Hostname)很像基本路由协议(Router_Route),我们能够得到变量,设置默认值,同时还能通过正则匹配,在这里的正则匹配我们过掉了www。

  6)在配置文件中配置Zend_Config
  当我们有许多路由协议的时候,管理他们就开始变得很棘手了,这样我们就可以通过路由器来调用配置文件。

  1. [production]
  2. routes.rss.type = "Zend_Controller_Router_Route_Static"
  3. routes.rss.route = "products/rss"
  4. routes.rss.defaults.controller = feed
  5. routes.rss.defaults.action = rss
  6. routes.oldproducts.type = "Zend_Controller_Router_Route_Regex"
  7. routes.oldproducts.route = "products.php/category/(/d+)/product/(/d+)"
  8. routes.oldproducts.defaults.controller = products
  9. routes.oldproducts.defaults.action = old
  10. routes.oldproducts.map.categoryID = 1
  11. routes.oldproducts.map.productID = 2
  12. routes.product.type = "Zend_Controller_Router_Route_Regex"
  13. routes.product.route = "product/([a-zA-Z-_0-9]+)/(/d+)-([a-zA-Z-_0-9]+).html"
  14. routes.product.defaults.controller = products
  15. routes.product.defaults.action = view
  16. routes.product.map.categoryIdent = 1
  17. routes.product.map.productID = 2
  18. routes.product.map.productIdent = 3
  19. routes.product.reverse = "product/%s/%d-%s.html"
  20. routes.user.route = "user/profile/:username/*"
  21. routes.user.defaults.controller = user
  22. routes.user.defaults.action = profile
  23. routes.user.defaults.username = "Unknown"
  24. routes.user.reqs.username = "([a-zA-Z-_0-9]+)"
复制代码


一旦我们创建了一个ini文件,我们就能把它加载到路由器中:

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


   7)总结
    路由器属于ZF mvc组件中很重要的一个,我们上面描述的这些路由是经常可以用到的。可能在一开始的时候就去用router会感觉不适应,你可以先熟悉了解它,但我希望 你还是对这个有个比较详细的了解,再次建议,你应该好好的用一下上面的路由,如果你想掌握它的话!!
4、Dispatch----------------派遣器
  1)概述:派遣器主要负责我们实际调用动作控制器,事实上,派遣器是一个MCV的内部组件,并且Front Controller和它交互最密切!
  2)设计:派遣器主要是负责派遣正确的动作控制器中的动作方法,意味着它必须加载所有的控制器类,并且实例化他们,并且调用其中的Action。派遣 器把持着所有的MVC组建的命名的规则的设置,这些设置包括默认的module,controller,action 命名。派遣器像其他MVC组件样,同样提供了一个接口和抽象类供我们创建我们自己的或者继承父类的派遣 器:a.Zend_Controller_Dispatcher_Interface b.Zend_Controller_Dispatcher_Abstract.
  3)请求被派遣的过程:
    请求派遣过程就是派遣器Dispatcher从请求对象Request object中提取出Module,Controller,Action来,并且调用其中的Action Controller;该过程牵扯到了3个组件:Front Controller,Dispatcher,Request object;派遣的过程是发生在派遣循环中;该循环大体过程是:
    a.前端控制器开始派遣循环
    b.前端控制器调用派遣器
    c.派遣器获取Request object请求对象,并分析它
    d.派遣器从请求对象中找到对应的Action Controller动作控制器名、
    e.派遣器尝试加载该动作控制器类
    f.动作控制器类加载成功,派遣器实例化动作控制器类
    g.派遣器器从请求对象中找到对应的Action动作名
    h.派遣器将派遣标志设为true,标志着派遣完成
    i.派遣器开始派遣动作控制器类中的Action方法
    j.派遣动作完成,派遣器检测请求对象Request object派遣完成标识是否为false,如果是false则表示还有派遣没有完成,派遣器就再次进入派遣循环过程中;
派遣标识案例说明:

  1. //_forward()方法使用
  2. class IndexController extends Zend_Controller_Action{
  3.   public function indexAction(){
  4.     $this->_forward('index', 'index', 'product');
  5.   }
  6. }
  7. //同_forward()方法实现机理一样的代码
  8. class IndexController extends Zend_Controller_Action{
  9.   public function indexAction(){
  10.     $request = $this->getRequest();
  11.     $request->setModuleName('product')
  12.        ->setControllerName('index')
  13.        ->setActionName('index')
  14.        ->setDispatched(false);
  15.     }
  16. }
复制代码


注意到可以通过设置请求对象中的派遣标识就可以再次进入派遣循环中!!
  几乎所有与派遣器(Dispatch)相关的都是影响到它的默认行为,比如我们之前在前端控制器中所见的:setDefaultAction (string $action)、setDefaultControllerName (string $controller)、setDefaultModule (string $module),他们都是通过前端控制器调用,然后代理到派遣器中正确的方法。在这其中我们要注意一个重要的方法setParam(),我们可以通过它 来将一个变量或者一个对象传递到所有的控制器中:

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


   要注意的一个问题就是设置变量的时间,因为有时候我们会遇见这样的问题:就是为什么我们已经使用了$front->setParam(),但它没有设置到我们的值。这个原因就是当$front->dispatch()被调用时,变量从前端控制器传递到派遣器的过程是发生在routeStartup之前时间被触发 因此,任何前端控制器变量在这一点之后再来设置就无法将变量传递到派遣器中和动作控制器中! 派遣器和派遣过程是zf MVC实现过程中一个很重要的部分,同时它也允许控制如何派遣它以及何时派遣它(比如可以通过派遣标识来设置)。接下来让我看看请求对象(Request object)。
5、Http Request object 请求对象
  1)概述:请求对象,它提供给我们一种方式,这种方式可以封包我们的一个请求,并且可以让其他MVC组件(Front Controller、Router、Dispatcher、Response)可以与之交互。没有它的话,我的应用将不会工作!!
  2)设计:请求可以由许多方式产生(HTTP,CLI等),当一个请求发出的时候与之相关的请求对象也产生了!所有的请求对象都是基于 Zend_Controller_Request_Abstract抽象类设计的,这个抽象类提供给其他MVC组件操作的基本的方法,这些基本方法包括设 置将被派遣的module、controller以及action名字。它也包括设置请求变量和设置派遣状态(这点在我们之前的的Dispatcher中 见过的)。请求对象被设计成为抽象的意味着我们很容易的通过继承这个抽象类来创建我们自己的请求,同时也意味着ZF也没有锁定任何请求的环境,因此,我们 能够使用ZF MVC组件在HTTP、CLI或者我们喜欢的任何指定的环境。
  3)默认情况:
  默认的请求对象是使用Zend_Controller_Request_Http,默认就被注册到了前端控制器中。这个HTTP请求对象被设计成在HTTP环境下,因此它包含而外的属性比如像$_GET和$_POST数据,同时我们也可以使用下面的请求对象:
  a.Zend_Controller_Request_Simple;
  b.Zend_Controller_Request_Apache404;
Zend_Controller_Request_Simple提供了抽象类中的方法,被用于CLI MVC操作!
Zend_Controller_Request_Apache404是继承了默认的HTTP对象(Zend_Controller_Reuqest_Http),它提供了在重写过程中用于替代mod_rewrite或者PT标志的HTTP函数。
  4)使用请求对象:
   请求对象可以被前端控制器(Front Controller)和动作控制器(Action Controller)通过调用getRequest()方法来调用请求对象!请求对象即可以通过前端控制器实例化(默认情况),也可以被我们自定义来实例化:

  1. //通过前端控制器拿请求对象
  2. $front = Zend_Controller_Front::getInstance();
  3. $request = $front->getRequest();
  4. //在控制器内,通过动作控制器拿:
  5. $this->getRequest();
复制代码


我们自定义请求对象:

  1. $front = Zend_Controller_Front::getInstance();
  2. $myRequest = new My_Controller_Request_Custom();
  3. //通过前端控制器的setRequest()方法来自定义我们自己的请求对象
  4. $request = $front->setRequest($myRequest);
复制代码


我们设置自定义的请求对象,要注意一点,要在派遣过程中的routeStartup之前就设置!!
  一旦我们有了一个请求对象,我们就可以设置或者获得请求对象中的属性,最重要的几个:

  • getModuleName() and setModuleName()
  • getControllerName() and setControllerName()
  • getActionName() and setActionName()
  • isDispatched() and setDispatched()


所有的这些方法得到或者设置信息被派遣器用来决定什么模块、控制器、动作将被派遣,我们在我们上面的派遣器部分见到过了,所有这里就不再重复。
  请求对象提供给我们一种方法设置和得到他们的变量,这些方法如下:

  • getParam() and setParam()
  • getParams() and setParams()
  • getUserParams() and getUserParam()

  这些方法可以让人能够存储有关环境和请求的信息,在用户变量和环境变量之间很有大的不同。用户变量是通过setParam()或者setParams()方法直接被设置到请求的对象中,其他变量自动 的从环境中被创建并被请求所设置。
  
      5)总结:请求对象是在前端控制器,路由器,分发器,以及控制类间传递的简单值对象。请求对象封装了请求的模块,控制器,动作以及可选的参数,还包括其 他的请求环境,如HTTP,CLI,PHP-GTK。 请求对象先被传入到前端控制器。如果没有提供请求对象,它将在分发过程的开始、任何路由过程发生之前实例化。请求对象将被传递到分发链中的每个对象。而 且,请求对象在测试中是很有用的。开发人员可根据需要搭建请求环境,包括模块、控制器、动作、参数、URI等等,并且将其传入前端控制器来测试程序流向。 如果与响应对象配合,可以对MVC程序进行精确巧妙的单元测试(unit testing)。
6、The Response object 响应对象
  1)概述:接下来,我们进入到我们的最后一个组件,也是派遣过程中的最后一个部分---响应对象,响应对象逻辑上是请求对象的搭档.目的在于收集消息体和/或消息头,因而可能返回大批的结果。
  
      2)默认情况:和请求对象一样,响应对象也可以由许多方式产生。所有的请对象都是基于Zend_Controller_Response_Abstract。 响 应可用在整个派遣过程中,我们能够在派遣的过程中增加它,我们后面会有例子说明的。响应对象中可以包含头部、异常、以及其他数据像用于响应一个请求生成的 HTML代码。另一个与请求对象相似的地方是请求对象也是针对环境的,意思就是我们可以针对不同的环境给不同的响应类型,同时也意味着我们的MVC组件能 够被用在CLI、HTTP或者是其他特定的环境。我们同样也很容易的继承响应对象抽象类 (Zend_Controller_Response_Abstract)并且实例化他们,默认环境下,我们使用的Response Object响应对象是Zend_Controller_Response_Http(),这些我们在一开始的时候就见过了!!
  
     3)使用响应对象(Request Object):响应对象处理3中类型的数据,他们分别是:异常(exception)、头部(headers)、响应主体(response body);响应主体可以是针对请求返回的任何信息,比如一个请求是一个web页面 ,我们可以返回html、xml 、txt、binary Image数据等。设置response body,我们可以通过响应对象的setBody()方法:

  1. $response = new Zend_Controller_Response_Http();
  2. $response->setBody('<h1>Default Body</h1>');
复制代码


当使用了setBody()方法后,响应对象(Response Object)就会产生一个默认的段(default segment),这点常常来自view视图,同时它也将覆盖掉我们在响应对象中原有的段,像上面,这样就可能产生一个下面的段:

  1. array(
  2.   'default' => '<h1>Default Body</h1>'
  3. );
复制代码


如果我们给setBody()增加一个参数,该参数就是段名:

  1. //向response object中写入2个段
  2. $response = new Zend_Controller_Response_Http();
  3. $response->setBody('<h1>Default Body</h1>');
  4. $response->setBody('<p>More body</p>', 'test');
  5. //这将会提供2个段:
  6. array(
  7.   'default' => '<h1>Default Body</h1>',
  8.   'test' => '<p>More body</p>'
  9. );
复制代码


除了设置它以外,响应对象还提供给我一些其他的方法来控制段的位置:

  • append (string $name, string $content)
  • appendBody (string $content, null|string $name = null)
  • prepend (string $name, string $content)
  • insert (string $name, string $content, string $parent = null,boolean $before = false)

考虑下下面的例子(你可以在自己的zf测试项目 中尝试下,):

  1. $response = new Zend_Controller_Response_Http();
  2. $response->setBody('<h1>Default Body</h1>');
  3. $response->setBody('<p>More body</p>', 'test');
  4. $response->appendBody('<p>append to test segment</p>','test');
  5. $response->prepend('header','<html><body>');
  6. $response->append('footer','</body></html>');
  7. $response->insert('extra','<h2>Extra body</h2>','default', false);
  8. $response->insert('more', '<p>Before footer</p>', 'footer', true);
复制代码


通过上面的设置,我们将得到下面的段:

  1. array(
  2.   'header' => '<html><body>',
  3.   'default' => '<h1>Default Body</h1>',
  4.   'extra' => '<h2>Extra body</h2>',
  5.   'test' => '<p>More body</p><p>append to test segment</p>',
  6.   'more' => '<p>Before footer</p>',
  7.   'footer' => '</body></html>',
  8. );
复制代码


另外前端控制器可能传递任何异常到响应对象,允许开发人员优美的处理异常。可以通过设置 Zend_Controller_Front::throwExceptions(true)覆盖这项功能:

  1. $front->throwExceptions(true);
复制代码


如果要发送响应输出 包括消息头,使用sendResponse()。

  1. $response->sendResponse();
复制代码


默认地,前端控制器完成分发请求后调用sendResponse();一般地,你不需要调用它。但是,如果你想处理响应或者用它来测试你可以使用 Zend_Controller_Front::returnResponse(true)设置returnResponse 标志覆盖默认行为:

  1. $front->returnResponse(true);
  2. $response = $front->dispatch();
  3. // 可以在这里设置日志什么的..
  4. //...
  5. $response->sendResponse();
复制代码



     4)总结:响应对象的目的首先在于从大量的动作和插件中收集消息头和内容,然后返回到客户端;其次,响应对象也收集发生的任何异常,以处理或者返回 这些异常,再或者对终端用户隐藏它们。响应的基类是Zend_Controller_Response_Abstract,创建的任何子类必须继承这个类 或它的衍生类。前面的章节中已经列出了大量可用的方法。子类化响应对象的原因包括基于请求环境修改返回的内容的输出方式(例如:在CLI和PHP-GTK 请求中不发送消息头)增加返回存储在命名片段中内容的最终视图的功能等等。   

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值