# 控制器
控制器是?[MVC](http://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller)?模式中的一部分, 是繼承yii\base\Controller類的對象,負責處理請求和生成響應。 具體來說,控制器從[應用主體](http://www.yiichina.com/doc/guide/2.0/structure-applications)接管控制后會分析請求數據并傳送到[模型](http://www.yiichina.com/doc/guide/2.0/structure-models), 傳送模型結果到[視圖](http://www.yiichina.com/doc/guide/2.0/structure-views),最后生成輸出響應信息。
## 操作
控制器由?*操作*?組成,它是執行終端用戶請求的最基礎的單元,一個控制器可有一個或多個操作。
如下示例顯示包含兩個操作`view`?and?`create`?的控制器`post`:
~~~
namespace app\controllers;
use Yii;
use app\models\Post;
use yii\web\Controller;
use yii\web\NotFoundHttpException;
class PostController extends Controller
{
public function actionView($id)
{
$model = Post::findOne($id);
if ($model === null) {
throw new NotFoundHttpException;
}
return $this->render('view', [
'model' => $model,
]);
}
public function actionCreate()
{
$model = new Post;
if ($model->load(Yii::$app->request->post()) && $model->save()) {
return $this->redirect(['view', 'id' => $model->id]);
} else {
return $this->render('create', [
'model' => $model,
]);
}
}
}
~~~
在操作?`view`?(定義為?`actionView()`?方法)中, 代碼首先根據請求模型ID加載?[模型](http://www.yiichina.com/doc/guide/2.0/structure-models), 如果加載成功,會渲染名稱為`view`的[視圖](http://www.yiichina.com/doc/guide/2.0/structure-views)并顯示,否則會拋出一個異常。
在操作?`create`?(定義為?`actionCreate()`?方法)中, 代碼相似. 先將請求數據填入[模型](http://www.yiichina.com/doc/guide/2.0/structure-models), 然后保存模型,如果兩者都成功,會跳轉到ID為新創建的模型的`view`操作,否則顯示提供用戶輸入的`create`視圖。
## 路由
終端用戶通過所謂的*路由*尋找到操作,路由是包含以下部分的字符串:
* 模型ID: 僅存在于控制器屬于非應用的[模塊](http://www.yiichina.com/doc/guide/2.0/structure-modules);
* 控制器ID: 同應用(或同模塊如果為模塊下的控制器)下唯一標識控制器的字符串;
* 操作ID: 同控制器下唯一標識操作的字符串。
路由使用如下格式:
~~~
ControllerID/ActionID
~~~
如果屬于模塊下的控制器,使用如下格式:
~~~
ModuleID/ControllerID/ActionID
~~~
如果用戶的請求地址為?`http://hostname/index.php?r=site/index`, 會執行`site`?控制器的`index`?操作。 更多關于處理路由的詳情請參閱?[路由](http://www.yiichina.com/doc/guide/2.0/runtime-routing)?一節。
## 創建控制器
在yii\web\Application網頁應用中,控制器應繼承yii\web\Controller 或它的子類。 同理在yii\console\Application控制臺應用中,控制器繼承yii\console\Controller 或它的子類。 如下代碼定義一個?`site`?控制器:
~~~
namespace app\controllers;
use yii\web\Controller;
class SiteController extends Controller
{
}
~~~
### 控制器ID
通常情況下,控制器用來處理請求有關的資源類型,因此控制器ID通常為和資源有關的名詞。 例如使用`article`作為處理文章的控制器ID。
控制器ID應僅包含英文小寫字母、數字、下劃線、中橫杠和正斜杠, 例如?`article`?和?`post-comment`?是真是的控制器ID,`article?`,?`PostComment`,?`admin\post`不是控制器ID。
控制器Id可包含子目錄前綴,例如?`admin/article`?代表 yii\base\Application::controllerNamespace控制器命名空間下?`admin`子目錄中?`article`?控制器。 子目錄前綴可為英文大小寫字母、數字、下劃線、正斜杠,其中正斜杠用來區分多級子目錄(如`panels/admin`)。
### 控制器類命名
控制器ID遵循以下規則衍生控制器類名:
* 將用正斜杠區分的每個單詞第一個字母轉為大寫。注意如果控制器ID包含正斜杠,只將最后的正斜杠后的部分第一個字母轉為大寫;
* 去掉中橫杠,將正斜杠替換為反斜杠;
* 增加`Controller`后綴;
* 在前面增加yii\base\Application::controllerNamespace控制器命名空間.
下面為一些示例,假設yii\base\Application::controllerNamespace控制器命名空間為?`app\controllers`:
* `article`?對應?`app\controllers\ArticleController`;
* `post-comment`?對應?`app\controllers\PostCommentController`;
* `admin/post-comment`?對應?`app\controllers\admin\PostCommentController`;
* `adminPanels/post-comment`?對應?`app\controllers\adminPanels\PostCommentController`.
控制器類必須能被?[自動加載](http://www.yiichina.com/doc/guide/2.0/concept-autoloading),所以在上面的例子中, 控制器`article`?類應在?[別名](http://www.yiichina.com/doc/guide/2.0/concept-aliases)?為`@app/controllers/ArticleController.php`的文件中定義, 控制器`admin/post2-comment`應在`@app/controllers/admin/Post2CommentController.php`文件中。
> 補充: 最后一個示例?`admin/post2-comment`?表示你可以將控制器放在 yii\base\Application::controllerNamespace控制器命名空間下的子目錄中, 在你不想用?[模塊](http://www.yiichina.com/doc/guide/2.0/structure-modules)?的情況下給控制器分類,這種方式很有用。
### 控制器部署
可通過配置 yii\base\Application::controllerMap 來強制上述的控制器ID和類名對應, 通常用在使用第三方不能掌控類名的控制器上。
配置?[應用配置](http://www.yiichina.com/doc/guide/2.0/structure-applications#application-configurations)?中的[application configuration](http://www.yiichina.com/doc/guide/2.0/structure-applications#application-configurations),如下所示:
~~~
[
'controllerMap' => [
// 用類名申明 "account" 控制器
'account' => 'app\controllers\UserController',
// 用配置數組申明 "article" 控制器
'article' => [
'class' => 'app\controllers\PostController',
'enableCsrfValidation' => false,
],
],
]
~~~
### 默認控制器
每個應用有一個由yii\base\Application::defaultRoute屬性指定的默認控制器; 當請求沒有指定?[路由](http://www.yiichina.com/doc/guide/2.0/structure-controllers#ids-routes),該屬性值作為路由使用。 對于yii\web\Application網頁應用,它的值為?`'site'`, 對于 yii\console\Application控制臺應用,它的值為?`help`, 所以URL為`http://hostname/index.php`?表示由?`site`?控制器來處理。
可以在?[應用配置](http://www.yiichina.com/doc/guide/2.0/structure-applications#application-configurations)?中修改默認控制器,如下所示:
~~~
[
'defaultRoute' => 'main',
]
~~~
## 創建操作
創建操作可簡單地在控制器類中定義所謂的?*操作方法*?來完成,操作方法必須是以`action`開頭的公有方法。 操作方法的返回值會作為響應數據發送給終端用戶,如下代碼定義了兩個操作?`index`?和?`hello-world`:
~~~
namespace app\controllers;
use yii\web\Controller;
class SiteController extends Controller
{
public function actionIndex()
{
return $this->render('index');
}
public function actionHelloWorld()
{
return 'Hello World';
}
}
~~~
### 操作ID
操作通常是用來執行資源的特定操作,因此,操作ID通常為動詞,如`view`,?`update`等。
操作ID應僅包含英文小寫字母、數字、下劃線和中橫杠,操作ID中的中橫杠用來分隔單詞。 例如`view`,?`update2`,?`comment-post`是真實的操作ID,`view?`,?`Update`不是操作ID.
可通過兩種方式創建操作ID,內聯操作和獨立操作. An inline action is 內聯操作在控制器類中定義為方法;獨立操作是繼承yii\base\Action或它的子類的類。 內聯操作容易創建,在無需重用的情況下優先使用; 獨立操作相反,主要用于多個控制器重用,或重構為[擴展](http://www.yiichina.com/doc/guide/2.0/structure-extensions)。
### 內聯操作
內聯操作指的是根據我們剛描述的操作方法。
操作方法的名字是根據操作ID遵循如下規則衍生:
* 將每個單詞的第一個字母轉為大寫;
* 去掉中橫杠;
* 增加`action`前綴.
例如`index`?轉成?`actionIndex`,?`hello-world`?轉成?`actionHelloWorld`。
> 注意: 操作方法的名字*大小寫敏感*,如果方法名稱為`ActionIndex`不會認為是操作方法, 所以請求`index`操作會返回一個異常,也要注意操作方法必須是公有的,私有或者受保護的方法不能定義成內聯操作。
因為容易創建,內聯操作是最常用的操作,但是如果你計劃在不同地方重用相同的操作, 或者你想重新分配一個操作,需要考慮定義它為*獨立操作*。
### 獨立操作
獨立操作通過繼承yii\base\Action或它的子類來定義。 例如Yii發布的yii\web\ViewAction和yii\web\ErrorAction都是獨立操作。
要使用獨立操作,需要通過控制器中覆蓋yii\base\Controller::actions()方法在*action map*中申明,如下例所示:
~~~
public function actions()
{
return [
// 用類來申明"error" 操作
'error' => 'yii\web\ErrorAction',
// 用配置數組申明 "view" 操作
'view' => [
'class' => 'yii\web\ViewAction',
'viewPrefix' => '',
],
];
}
~~~
如上所示,?`actions()`?方法返回鍵為操作ID、值為對應操作類名或數組[configurations](http://www.yiichina.com/doc/guide/2.0/concept-configurations)?的數組。 和內聯操作不同,獨立操作ID可包含任意字符,只要在`actions()`?方法中申明.
為創建一個獨立操作類,需要繼承yii\base\Action 或它的子類,并實現公有的名稱為`run()`的方法,?`run()`?方法的角色和操作方法類似,例如:
~~~
<?php
namespace app\components;
use yii\base\Action;
class HelloWorldAction extends Action
{
public function run()
{
return "Hello World";
}
}
~~~
### 操作結果
操作方法或獨立操作的`run()`方法的返回值非常重要,它表示對應操作結果。
返回值可為?[響應](http://www.yiichina.com/doc/guide/2.0/runtime-responses)?對象,作為響應發送給終端用戶。
* 對于yii\web\Application網頁應用,返回值可為任意數據, 它賦值給yii\web\Response::data, 最終轉換為字符串來展示響應內容。
* 對于yii\console\Application控制臺應用,返回值可為整數, 表示命令行下執行的 yii\console\Response::exitStatus 退出狀態。
在上面的例子中,操作結果都為字符串,作為響應數據發送給終端用戶,下例顯示一個操作通過 返回響應對象(因為yii\web\Controller::redirect()方法返回一個響應對象)可將用戶瀏覽器跳轉到新的URL。
~~~
public function actionForward()
{
// 用戶瀏覽器跳轉到 http://example.com
return $this->redirect('http://example.com');
}
~~~
### 操作參數
內聯操作的操作方法和獨立操作的?`run()`?方法可以帶參數,稱為*操作參數*。 參數值從請求中獲取,對于yii\web\Application網頁應用, 每個操作參數的值從`$_GET`中獲得,參數名作為鍵; 對于yii\console\Application控制臺應用, 操作參數對應命令行參數。
如下例,操作`view`?(內聯操作) 申明了兩個參數?`$id`?和?`$version`。
~~~
namespace app\controllers;
use yii\web\Controller;
class PostController extends Controller
{
public function actionView($id, $version = null)
{
// ...
}
}
~~~
操作參數會被不同的參數填入,如下所示:
* `http://hostname/index.php?r=post/view&id=123`:?`$id`?會填入`'123'`,`$version`?仍為 null 空因為沒有`version`請求參數;
* `http://hostname/index.php?r=post/view&id=123&version=2`: $id`?和?`$version`?分別填入?`'123'`?和?`'2'`;
* `http://hostname/index.php?r=post/view`: 會拋出yii\web\BadRequestHttpException 異常 因為請求沒有提供參數給必須賦值參數`$id`;
* `http://hostname/index.php?r=post/view&id[]=123`: 會拋出yii\web\BadRequestHttpException 異常 因為`$id`?參數收到數字值?`['123']`而不是字符串.
如果想讓操作參數接收數組值,需要指定$id為`array`,如下所示:
~~~
public function actionView(array $id, $version = null)
{
// ...
}
~~~
現在如果請求為?`http://hostname/index.php?r=post/view&id[]=123`, 參數?`$id`?會使用數組值`['123']`, 如果請求為`http://hostname/index.php?r=post/view&id=123`, 參數?`$id`?會獲取相同數組值,因為無類型的`'123'`會自動轉成數組。
上述例子主要描述網頁應用的操作參數,對于控制臺應用,更多詳情請參閱[控制臺命令](http://www.yiichina.com/doc/guide/2.0/tutorial-console)。
### 默認操作
每個控制器都有一個由 yii\base\Controller::defaultAction 屬性指定的默認操作, 當[路由](http://www.yiichina.com/doc/guide/2.0/structure-controllers#ids-routes)?只包含控制器ID,會使用所請求的控制器的默認操作。
默認操作默認為?`index`,如果想修改默認操作,只需簡單地在控制器類中覆蓋這個屬性,如下所示:
~~~
namespace app\controllers;
use yii\web\Controller;
class SiteController extends Controller
{
public $defaultAction = 'home';
public function actionHome()
{
return $this->render('home');
}
}
~~~
## 控制器生命周期
處理一個請求時,[應用主體](http://www.yiichina.com/doc/guide/2.0/structure-applications)?會根據請求[路由](http://www.yiichina.com/doc/guide/2.0/structure-controllers#routes)創建一個控制器,控制器經過以下生命周期來完成請求:
1. 在控制器創建和配置后,yii\base\Controller::init() 方法會被調用。
2. 控制器根據請求操作ID創建一個操作對象:
* 如果操作ID沒有指定,會使用yii\base\Controller::defaultAction默認操作ID;
* 如果在yii\base\Controller::actions()找到操作ID,會創建一個獨立操作;
* 如果操作ID對應操作方法,會創建一個內聯操作;
* 否則會拋出yii\base\InvalidRouteException異常。
3. 控制器按順序調用應用主體、模塊(如果控制器屬于模塊)、控制器的?`beforeAction()`?方法;
* 如果任意一個調用返回false,后面未調用的`beforeAction()`會跳過并且操作執行會被取消; action execution will be cancelled.
* 默認情況下每個?`beforeAction()`?方法會觸發一個?`beforeAction`?事件,在事件中你可以追加事件處理操作;
4. 控制器執行操作:
* 請求數據解析和填入到操作參數;
5. 控制器按順序調用控制器、模塊(如果控制器屬于模塊)、應用主體的?`afterAction()`?方法;
* 默認情況下每個?`afterAction()`?方法會觸發一個?`afterAction`?事件,在事件中你可以追加事件處理操作;
6. 應用主體獲取操作結果并賦值給[響應](http://www.yiichina.com/doc/guide/2.0/runtime-responses).
## 最佳實踐
在設計良好的應用中,控制器很精練,包含的操作代碼簡短; 如果你的控制器很復雜,通常意味著需要重構,轉移一些代碼到其他類中。
歸納起來,控制器
* 可訪問?[請求](http://www.yiichina.com/doc/guide/2.0/runtime-requests)?數據;
* 可根據請求數據調用?[模型](http://www.yiichina.com/doc/guide/2.0/structure-models)?的方法和其他服務組件;
* 可使用?[視圖](http://www.yiichina.com/doc/guide/2.0/structure-views)?構造響應;
* 不應處理應被[模型](http://www.yiichina.com/doc/guide/2.0/structure-models)處理的請求數據;
* 應避免嵌入HTML或其他展示代碼,這些代碼最好在?[視圖](http://www.yiichina.com/doc/guide/2.0/structure-views)中處理.
- 介紹(Introduction)
- 關于 Yii(About Yii)
- 從 Yii 1.1 升級(Upgrading from Version 1.1)
- 入門(Getting Started)
- 安裝 Yii(Installing Yii)
- 運行應用(Running Applications)
- 第一次問候(Saying Hello)
- 使用 Forms(Working with Forms)
- 玩轉 Databases(Working with Databases)
- 用 Gii 生成代碼(Generating Code with Gii)
- 更上一層樓(Looking Ahead)
- 應用結構(Application Structure)
- 結構概述(Overview)
- 入口腳本(Entry Scripts)
- 應用(Applications)
- 應用組件(Application Components)
- 控制器(Controllers)
- 模型(Models)
- 視圖(Views)
- 模塊(Modules)
- 過濾器(Filters)
- 小部件(Widgets)
- 前端資源(Assets)
- 擴展(Extensions)
- 請求處理(Handling Requests)
- 運行概述(Overview)
- 引導(Bootstrapping)
- 路由引導與創建 URL(Routing and URL Creation)
- 請求(Requests)
- 響應(Responses)
- Sessions and Cookies
- 錯誤處理(Handling Errors)
- 日志(Logging)
- 關鍵概念(Key Concepts)
- 組件(Components)
- 屬性(Properties)
- 事件(Events)
- 行為(Behaviors)
- 配置(Configurations)
- 別名(Aliases)
- 類自動加載(Class Autoloading)
- 服務定位器(Service Locator)
- 依賴注入容器(Dependency Injection Container)
- 配合數據庫工作(Working with Databases)
- 數據庫訪問(Data Access Objects): 數據庫連接、基本查詢、事務和模式操作
- 查詢生成器(Query Builder): 使用簡單抽象層查詢數據庫
- 活動記錄(Active Record): 活動記錄對象關系映射(ORM),檢索和操作記錄、定義關聯關系
- 數據庫遷移(Migrations): 在團體開發中對你的數據庫使用版本控制
- Sphinx
- Redis
- MongoDB
- ElasticSearch
- 接收用戶數據(Getting Data from Users)
- 創建表單(Creating Forms)
- 輸入驗證(Validating Input)
- 文件上傳(Uploading Files)
- 收集列表輸入(Collecting Tabular Input)
- 多模型同時輸入(Getting Data for Multiple Models)
- 顯示數據(Displaying Data)
- 格式化輸出數據(Data Formatting)
- 分頁(Pagination)
- 排序(Sorting)
- 數據提供器(Data Providers)
- 數據小部件(Data Widgets)
- 操作客戶端腳本(Working with Client Scripts)
- 主題(Theming)
- 安全(Security)
- 認證(Authentication)
- 授權(Authorization)
- 處理密碼(Working with Passwords)
- 客戶端認證(Auth Clients)
- 安全領域的最佳實踐(Best Practices)
- 緩存(Caching)
- 概述(Overview)
- 數據緩存(Data Caching)
- 片段緩存(Fragment Caching)
- 分頁緩存(Page Caching)
- HTTP 緩存(HTTP Caching)
- RESTful Web 服務
- 快速入門(Quick Start)
- 資源(Resources)
- 控制器(Controllers)
- 路由(Routing)
- 格式化響應(Response Formatting)
- 授權驗證(Authentication)
- 速率限制(Rate Limiting)
- 版本化(Versioning)
- 錯誤處理(Error Handling)
- 開發工具(Development Tools)
- 調試工具欄和調試器(Debug Toolbar and Debugger)
- 使用 Gii 生成代碼(Generating Code using Gii)
- TBD 生成 API 文檔(Generating API Documentation)
- 測試(Testing)
- 概述(Overview)
- 搭建測試環境(Testing environment setup)
- 單元測試(Unit Tests)
- 功能測試(Functional Tests)
- 驗收測試(Acceptance Tests)
- 測試夾具(Fixtures)
- 高級專題(Special Topics)
- 高級應用模版(Advanced Project Template)
- 從頭構建自定義模版(Building Application from Scratch)
- 控制臺命令(Console Commands)
- 核心驗證器(Core Validators)
- 國際化(Internationalization)
- 收發郵件(Mailing)
- 性能優化(Performance Tuning)
- 共享主機環境(Shared Hosting Environment)
- 模板引擎(Template Engines)
- 集成第三方代碼(Working with Third-Party Code)
- 小部件(Widgets)
- Bootstrap 小部件(Bootstrap Widgets)
- jQuery UI 小部件(jQuery UI Widgets)
- 助手類(Helpers)
- 助手一覽(Overview)
- Array 助手(ArrayHelper)
- Html 助手(Html)
- Url 助手(Url)