控制器
==========

`控制器` 是 [CController] 或其子類別的實體。它在當用戶請求時由應用程式建立。
當一個控制器運行時，它執行所請求的動作，動作通常會載入所必要的模型並呈現相應的視圖。
`動作` 最簡單的形式，就是一個名字以 `action` 開頭的控制器類別行為。

控制器通常有一個預設的動作。當用戶的請求未指定要執行的動作時，預設動作將被執行。
預設情況下，預設的動作名為 `index`。它可以通過設置 [CController::defaultAction] 修改。

如下是一個 `Site` 控制器，擁有一個 `index` 動作 (預設動作) 和一個 `contact` 動作。

~~~
[php]
class SiteController extends CController
{
	public function actionIndex()
	{
		// ...
	}

	public function actionContact()
	{
		// ...
	}
}
~~~


路由
-----

控制器和動作以 ID 識別。控制器 ID 是一種 'path/to/xyz' 的格式，對應相應的控制器類別檔案
`protected/controllers/path/to/XyzController.php`, 其中的標誌 `xyz`
應被替換為實際的名字 (例如 `post` 對應 `protected/controllers/PostController.php`).
動作 ID 是除去 `action` 前綴的動作方法名。例如，如果一個控制器類別含有一個名為 `actionEdit`
的方法，則相應的動作 ID 為 `edit`。

> Note|注意: 在 1.0.3 版本之前，控制器 ID 的格式為 `path.to.xyz` ，而不是 `path/to/xyz`。

用戶以路由的形式請求特定的控制器和動作。路由是由控制器 ID 和動作 ID 連接起來的，兩者以斜線分割。
例如，路由 `post/edit` 代表 `PostController` 及其 `edit` 動作。預設情況下，URL
`http://hostname/index.php?r=post/edit` 即請求此控制器和動作。

>Note|注意: 預設情況下，路由是大小寫敏感的，從版本 1.0.1 開始，可以通過設置應用程式配置中的
> [CUrlManager::caseSensitive] 為 false 使路由對大小寫不敏感。當在大小寫不敏感模式中時，
>要確保你遵循了相應的規則約定，即：包含控制器類別檔案的目錄名小寫，且 [控制器映射|CWebApplication::controllerMap]
>和 [動作映射|CController::actions] 中使用的鍵為小寫。

從 1.0.3 版本開始，應用程式可以含有 [模組（Module）](/doc/guide/basics.module). 模組中，控制器動作的路由格式為 `moduleID/controllerID/actionID` 。
更多詳情，請閱讀 [模組相關章節](/doc/guide/basics.module).


控制器實體化
------------------------

控制器實體在 [CWebApplication] 處理送來的請求時建立。指定了控制器 ID ，
應用程式將使用如下規則確定控制器的類別以及類別檔案的位置。

   - 如果指定了 [CWebApplication::catchAllRequest] , 控制器將基於此屬性建立，
而用戶指定的控制器 ID 將被忽略。這通常用於將應用程式設置為維護狀態並顯示一個靜態提示頁面。

   - 如果在 [CWebApplication::controllerMap] 中找到了 ID, 相應的控制器配置將被用於建立控制器實體。

   - 如果 ID 為 `'path/to/xyz'`的格式，控制器類別的名字將判斷為 `XyzController`，
相應的類別檔案則為 `protected/controllers/path/to/XyzController.php`。例如，
控制器 ID `admin/user` 將被解析為控制器類別 `UserController`，類別檔案是 `protected/controllers/admin/UserController.php`。
如果類別檔案不存在，將觸發一個 404 [CHttpException] 異常。

在使用了 [模組](/doc/guide/basics.module) (1.0.3 版後可用) 後，上述過程則稍有不同。
具體來說，應用程式將檢查此 ID 是否代表一個模組中的控制器。如果是的話，模組實體將被首先建立，然後建立模組中的控制器實體。


動作
------

如前文所述，動作可以被定義為一個以 `action` 單字作為前綴命名的方法。而更高級的方式是定義一個動作類別並讓控制器在收到請求時將其實體化。
這使得動作可以被復用，提高了可復用度。

要定義一個新動作類別，可用如下程式碼：

~~~
[php]
class UpdateAction extends CAction
{
	public function run()
	{
		// place the action logic here
	}
}
~~~

為了讓控制器注意到這個動作，我們要用如下方式覆蓋控制器類別的[actions()|CController::actions] 方法：

~~~
[php]
class PostController extends CController
{
	public function actions()
	{
		return array(
			'edit'=>'application.controllers.post.UpdateAction',
		);
	}
}
~~~

如上所示，我們使用了路徑別名 `application.controllers.post.UpdateAction` 指定動作類別檔案為
 `protected/controllers/post/UpdateAction.php`.

通過編寫基於類別的動作，我們可以將應用程式組織為模組的風格。例如，
如下目錄結構可用於組織控制器相關程式碼：

~~~
protected/
    controllers/
        PostController.php
        UserController.php
        post/
            CreateAction.php
            ReadAction.php
            UpdateAction.php
        user/
            CreateAction.php
            ListAction.php
            ProfileAction.php
            UpdateAction.php
~~~

### 動作參數綁定

從版本  1.1.4 開始，Yii 提供了對自動動作參數綁定的支援。
就是說，控制器動作可以定義命名的參數，參數的值將由 Yii 自動從 `$_GET` 提取。

為了詳細說明此功能，假設我們需要為 `PostController` 寫一個 `create` 動作。此動作需要兩個參數：

* `category`: 一個整數，代表文章（post）要發表在的那個分類別的 ID。
* `language`: 一個字串，代表帖子所使用的語言程式碼。

從 `$_GET` 中提取參數時，我們可以不再使用下面這種無聊的程式碼了：

~~~
[php]
class PostController extends CController
{
	public function actionCreate()
	{
		if(isset($_GET['category']))
			$category=(int)$_GET['category'];
		else
			throw new CHttpException(404,'invalid request');

		if(isset($_GET['language']))
			$language=$_GET['language'];
		else
			$language='en';

		// ... fun code starts here ...
	}
}
~~~

現在使用動作參數功能，我們可以更輕鬆的完成任務：

~~~
[php]
class PostController extends CController
{
	public function actionCreate($category, $language='en')
	{
		$category=(int)$category;

		// ... fun code starts here ...
	}
}
~~~

注意我們在動作方法 `actionCreate` 中增加了兩個參數。
這些參數的名字必須和我們想要從 `$_GET` 中提取的名字一致。
當用戶沒有在請求中指定 `$language` 參數時，這個參數會使用預設值 `en` 。
由於 `$category` 沒有預設值，如果用戶沒有在 `$_GET` 中提供 `category` 參數，
將會自動拋出一個 [CHttpException] (錯誤程式碼 400) 異常。

從 1.1.5 之後的版本, Yii 也支援對動作參數的陣列型別的偵測。這是藉由 PHP 的型別提示來完成，語法如下：

~~~
[php]
class PostController extends CController
{
	public function actionCreate(array $categories)
	{
		// Yii will make sure $categories be an array
	}
}
~~~

使用關鍵字 `array` 在參數 `$categories` 之前。所以，如果 `$_GET['categories']` 是一個字串，他就會被轉換成一個含有此字串的陣列。

> Note|注意: 如果參數的宣告沒有 `array` 的型別提示，這代表該參數一定是個
> 純量 (scalar) (例如，不是一個陣列). 在這種情況下，透過 `$_GET` 傳遞一個陣列參數
> 會引發 HTTP 例外。



過濾器
------

過濾器是一段程式碼，可被配置在控制器動作執行之前或之後執行。例如，存取控制過濾器將被執行以確保在執行請求的動作之前，用戶已通過身份驗證；性能過濾器可用於測量控制器執行所用的時間。

一個動作可以有多個過濾器。過濾器執行順序為它們出現在過濾器列表中的順序。過濾器可以阻止動作及後面其他過濾器的執行

過濾器可以定義為一個控制器類別的方法。方法名必須以 `filter` 開頭。例如，現有的 `filterAccessControl` 方法定義了一個名為 `accessControl` 的過濾器。
過濾器方法必須為如下結構：

~~~
[php]
public function filterAccessControl($filterChain)
{
	// 調用 $filterChain->run() 以繼續後續過濾器與動作的執行。
}
~~~

其中的 `$filterChain` (過濾器鏈)是一個 [CFilterChain] 的實體，代表與所請求動作相關的過濾器列表。在過濾器方法中，
我們可以調用 `$filterChain->run()` 以繼續執行後續過濾器和動作。

過濾器也可以是一個 [CFilter] 或其子類別的實體。如下程式碼定義了一個新的過濾器類別：

~~~
[php]
class PerformanceFilter extends CFilter
{
	protected function preFilter($filterChain)
	{
		// 動作被執行之前應用程式的邏輯
		return true; // 如果動作不應被執行，此處返回 false
	}

	protected function postFilter($filterChain)
	{
		// 動作執行之後應用程式的邏輯
	}
}
~~~

要對動作應用程式過濾器，我們需要覆蓋
`CController::filters()` 方法。此方法應返回一個過濾器配置數組。例如：

~~~
[php]
class PostController extends CController
{
	......
	public function filters()
	{
		return array(
			'postOnly + edit, create',
			array(
				'application.filters.PerformanceFilter - edit, create',
				'unit'=>'second',
			),
		);
	}
}
~~~

上述程式碼指定了兩個過濾器： `postOnly` 和 `PerformanceFilter`。
`postOnly` 過濾器是基於方法的（相應的過濾器方法已在 [CController] 中定義）；
而 `performanceFilter` 過濾器是基於物件的。路徑別名 `application.filters.PerformanceFilter`
指定過濾器類別檔案是 `protected/filters/PerformanceFilter`。我們使用一個數組配置
`PerformanceFilter` ，這樣它就可被用於初始化過濾器對象的屬性值。此處 `PerformanceFilter` 的 `unit` 屬性值將被初始為 `second`。

使用加減號，我們可指定哪些動作應該或不應該應用程式過濾器。上述程式碼中， `postOnly`
應只被應用程式於 `edit` 和 `create` 動作，而 `PerformanceFilter` 應被應用程式於 除了 `edit` 和 `create` 之外的動作。
如果過濾器配置中沒有使用加減號，則此過濾器將被應用於所有動作。

<div class="revision">$Id$</div>