第 7 章 Zend_Controller
上一页 |  | 下一页

## 第 7 章 Zend_Controller

## 7.1. 概述

### 7.1.1. 简介

Zend_Controller是使用MVC模式来构建一个站点的基础。Zend_Controller体系是一个轻量的，模块化和可扩展的体系。它只提供最核心的必要的部分，允许开发者有很大的自由来灵活地构建自己的站点。使用Zend_Controller的站点，其文件组织和代码结构会比较相似。

`Zend_Controller` 的工作流(workflow)是通过几个组件来实现的。虽然不需要完全理解这几个组件的含义，如果你对工作流程有点了解是很有帮助的。

- `Zend_Controller_Front`是Zend_Controller_Controller体系的组织者，它是FrontController设计模式的实现。Zend_Controller_Front处理服务器接受的所有请求，并最后负责将请求分配给各个ActionController(`Zend_Controller_Action`)

- `Zend_Controller_Request_Abstract`用于处理用户请求，提供各种类方法来设置和获得Controller和action的名称及各种请求的参数。另外，它可以跟踪其中的action是否已经被`Zend_Controller_Dispatcher`分配。本抽象类的子类可用于封装整个请求环境，允许router从中获取用户请求相关信息，或设置controller和action的名称。

  Zend Framework默认使用`Zend_Controller_Request_Http`类来处理用户请求，该类可用于访问HTTP请求相关信息（用户请求不一定通过HTTP，那么就需要你自己实现相关的类--Haohappy注），

- `Zend_Controller_Router_Interface`用于定义router。路由是将检查用户请求并决定由哪一个controller，和其中哪一个 Action来接受请求的过程。request对象中的Controller,action和可选的参数将被`Zend_Controller_Dispatcher`处理。路由只发生一次：当请求被服务器接收到时，在分配到第一个控制器之前。 (所谓router，和我们熟知的网络路由器的功能是很相似的，具有判断网络地址和选择路径的功能，这里就是用来定位到某个控制器中的某个方法 --Haohappy注)

  默认的router是`Zend_Controller_Router`，它将一个`Zend_Controller_Request_Http`指定的URI分解成controller,action和参数。例如：URL`http://localhost/foo/bar/key/value`将被分解成`foo` controller, `bar` action,并带有参数`key`，参数值为`value`.

- `Zend_Controller_Dispatcher_Interface`接口用于定义dispatcher（分配器，或称调度器、派遣器等）。 “分配”是指从request对象中获取controller和action的名称，并映射到controller文件/类及其中action类方法的过程。如果controller或action不存在，会将请求分配到默认的控制器和方法进行处理。

  实际上分配过程包括初始化controller类和调用类方法。和路由不一样，路由只发生一次，而分配是一个循环发生的过程。如果request对象的分配状态被重设为false，则循环就会重复，调用request中设置的方法。如果request对象的分配状态被设置成true，则分配过程结束。

  默认的dispatcher是`Zend_Controller_Dispatcher`。它规定了控制器类命名首字母大写，并以Controller结尾，而action方法则是首字母小写，以Action结尾，例如： `SomeFooController::barAction`. 在例子中，控制器是`somefoo`，而action是`bar`.

  另外，在加载一个控制器的时候，你可以指定一个`module`（模块）。有了module，我们可以将controller放在一个子目录中，而不用全部放在controllers目录下。要使用module，可以在front controller中设置参数 `useModules`：

      						$front->setParam('useModules', true);

  举个例子，看下面的URL:

      						http://example.com/user/news/action

  在上面的例子中，我们指定了'user'模块，并调用其中的news控制器。dispatcher会将其解释成`User_NewsController`类，并在`User/NewsController.php`文件中寻找该类。

  Module非常有用，当你想把代码分散到子目录中去的时候，或者使用第三方代码时，或者在不同的应用中重用相同的控制器时。

- `Zend_Controller_Action`是最基本的控制器。每个具体的控制器都是从Zend_Controller_Action类继承而来，是Zend_Controller_Action的子类，并且有自己的action方法。

- `Zend_Controller_Response_Abstract`定义了基础的响应类，用于收集并返回action的响应，包括响应的头部(header)和主体(body)，由于它使用了 `__toString()`方法，所以可以直接用echo来一次性输出所有header和内容。

  默认的响应类是`Zend_Controller_Response_Http`，它很适合于HTTP环境下使用。

Zend_Controller的工作流程相当简单。`Zend_Controller_Front`接收一个请求，然后由`Zend_Controller_Router`来决定分配给哪个controller。`Zend_Controller_Router`把URI分解，便于设定请求中的controller和action的名称。`Zend_Controller_Front`接着进入一个分配循环，调用Zend_Controller_Dispatcher，把dispatcher传给request，来调用请求中指定的具体的(或默认的)controller和action进行处理。在controller结束后，又把控制权交加给 `Zend_Controller_Front`。如果controller发现需要调用另一个controller（request的分配状态被清零），循环会一直继续直到另一次分配执行完毕。

### 7.1.2. Request Object Request对象

Request对象是一个简单的“值对象”(value object)，在 `Zend_Controller_Front`和router、dispatcher和controller间传递。它封装了controller、action名称及要传递给某个action的参数，还有请求环境中的其它信息，请求环境可以是HTTP、命令行或PHP-GTK等。

- controller的名称可以通过`getControllerName()` 和`setControllerName()`来访问和设置。

- action的名称可以通过`getActionName()` 和`setActionName()`来访问和设置。

- 传递给action的参数是一个关联数组，可以通过`getParams()`和 `setParams()`访问和设置，或者只访问和设置其中一个参数，可以用`getParam()` 和 `setParam()`。

根据请求的不同类型，不同的请求类中可能有各种可用的方法，例如默认的请求类是`Zend_Controller_Request_Http`，它有一些用于获取请求URI、路径信息，$_GET和$_POST参数等的类方法。

request对象被传递给front controller，或者如果没有提供request对象，会自动在分配过程一开始时实例化生成，在路由发生之前。request对象会被传递给dispatch循环中的每个对象。

另外，request对象在测试时非常有用。程序员可能会构造请求环境，包括controller、action、参数、URI等，并传递请求对象到front controller，来测试整个程序的工作流。同时使用request和response对象，精细和准确的单元测试将变成可能。

### 7.1.3. Route Process 路由过程

在你构建第一个控制器之前，你需要理解Zend_Controller_Router中的重定向过程是如何工作的。记住工作流程分为两步：一是路由（routing），只发生一次；二是分配（dispatching），循环过程。

`Zend_Controller_Front` 调用Zend_Controller_Router（或者你自己注册的router）来使一个URI映射到一个controller及其中的action上。`Zend_Controller_Router`从request对象中获取URI，并分解之，决定将调用的controller、action和其它URL参数，并把分解所得的这些结果存入request对象。

router使用很简单的方法来决定使用的controller及其action：

    				http://framework.zend.com/controller/action/

上面controller就是我们要采用的控制器，action就是我们要采用的action。

可选择地，参数可以在URI中定义，并传递给controller。格式为key/value　：

    				http://framework.zend.com/controller/action/key1/value1/

如果URL中controller或action/这部份没有写，`Zend_Controller_Dispatcher`会尝试从request对象的参数中获取相应的值，如果没有找到，则使用默认值。不论controller还是action，默认都是调用index。如：

    				http://framework.zend.com/roadmap/future/
    				Controller: roadmap
    				Action    : future

    				http://framework.zend.com/roadmap/
    				Controller: roadmap
    				Action    : index

    				http://framework.zend.com/
    				Controller: index
    				Action    : index

你也可以通过几种方式把controller放在子目录或模块下：

- 使用下划线_来命名控制器，例如：`http://framework.zend.com/admin_roadmap/future`将映射到Admin_RoadmapController控制器。如果你不想使用下划线，也可以使用其它分隔符号，通过dispatcher的setPathSeparator()方法可以设置。

- 通过设置front controller的`useModules`参数，你可以使用干净漂亮的URI来访问子目录下的控制器。上例将访问`http://framework.zend.com/admin/roadmap/future`。要达到这个目的，可以在front controller或者router中设置`useModules`参数：

      				$front->setParam('useModules', true);
      				// or
      				$router->setParam('useModules', true);

  这样的设置对于基本的router和RewriteRouter都有效。

| Flexibility 灵活性
如果你想得到更多灵活性，你可以看看这里： 第 7.4.3 节 “Zend_Controller_RewriteRouter”。

controller和其中的action名称，及任何参数都在request对象中设置。当`Zend_Controller_Front`进行dispatch循环时，request对象将被传递给 `Zend_Controller_Dispatcher`。

### 7.1.4. Dispatch Process 分配过程

“分配”（Dispatching，是指分发请求到具体的控制器的过程，也是调用控制器和方法的过程，从这个角度说理解为“调度”更为准确 --Haohappy注）是根据request对象(`Zend_Controller_Request_Abstract`)，从中得到controller和action名称及参数，然后实例化一个controller并调用其中方法的过程。如果没有发现controller和action的名称，它会使用默认值。 `Zend_Controller_Dispatcher`指定`index`作为默认值，但允许开发者自行指定默认值，可以通过`setDefaultController()`和`setDefaultAction()`方法。

调度过程发生于front controller内部的一个循环中。在调度发生之前，前端控制器把请求分解，得到控制器、方法的名称及参数，然后进行一个调度循环，分派请求。

在每次迭代的开始，request对象中都会有个标识变量(flag)来指示当前action是否已经被分配。如果一个action或者pre/postDispatch(分配前/后)插件清空了该标识变量，则分配过程会继续下去，尝试再次分配该请求。通过改变请求中的controller和/或action，或重设flag，程序员可以定义一定的“请求链”并执行。

The action controller method that controlls such dispatching is `_forward()`; call this method from any of the pre/postDispatch() or action methods, providing a controller, action, and optionally any additional parameters you may wish to send to the new action: 控制分配过程的类方法是 `_forward()`，在任何 pre/postDispatch()方法或者action方法中调用该方法，即可调用另一个action：

    			public function myAction()
    			{
    			  // 进行一些处理...
    			  //跳转到另一个action,FooController::barAction():
    			  $this->_forward('foo', 'bar', array('baz' => 'bogus'));
    			}

### 7.1.5. Response Object 响应对象

response对象与request对象从逻辑上说是相对应的，它的目的是收集服务器返回的内容的header和body。另外，front controller将把捕捉到的异常传递到response对象中，允许程序员优雅地处理异常。这个功能可以通过设设置 `Zend_Controller_Front::throwExceptions(true)`来实现：

    $front->throwExceptions(true);

由于response对象使用了`__toString()`方法，所以你可以很安全地用echo来输出该对象。也就是这样使用：

    echo $controller->getResponse();

    // or
    $response = $controller->getResponse();
    echo $response;

程序员应当在controller中使用response对的是，不要直接输出内容或头部，应该把这些输出放到response对象中去：

    // Within an action controller action:
    // Set a header
    $this->getResponse()
        ->setHeader('Content-Type', 'text/html')
        ->appendBody($content);

这样做，所有的header会在发送内容之前设置一次。

程序中是否发生异常，可以使用`isException()`来检查response对象的flag，并且通过`getException()`来得到该异常。另外，你也可以定制自己的response对象，使程序出错时转向到出错信息页、记录异常信息或格式化异常信息等。

在front controller进行dispatch()之后，你可以得到response对象，或者请求front controller返回response对象而不是直接显示输出。

    // retrieve post-dispatch:
    //得到dispatch后的响应：
    $front->dispatch();
    $response = $front->getResponse();
    if ($response->isException()) {
        // log, mail, etc...
    }

    // Or, have the front controller dispatch() process return it
    // 或者，让front controller在dispatch()执行过程中返回响应
    $front->returnResponse(true);
    $response = $front->dispatch();

    // do some processing...

    // finally, echo the response
    //最后，用echo输出响应信息
    echo $response;

默认地，异常信息不会被显示。要显示异常信息，需要通过调用`renderExceptions()`，或者启用front controller的throwExceptions()方法，例如：

    $response->renderExceptions(true);
    $front->dispatch($request, $response);

    // 或者
    $front->returnResponse(true);
    $response = $front->dispatch();
    $response->renderExceptions();
    echo $response;

    // 或者
    $front->throwExceptions(true);
    $front->dispatch();

上一页 |  | 下一页
6.4. Configuring Zend_Console_Getopt | 起始页 | 7.2. 入门