Bộ điều khiển (Controller)
===========

Controller thuộc một phần trong mẫu thiết kế [MVC](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller).
Controller là đối tượng được kế thừa từ class [[yii\base\Controller]] và chịu trách nhiệm xứ lý các yêu cầu và gửi phản hồi
. Đặc biệt, sau khi tiếp nhận các yêu cầu điều khiển từ [ứng dụng](structure-applications.md),
controllers sẽ phân tích thông tin yêu cầu được gửi đến, gửi dữ liệu qua [models](structure-models.md) để xử lý, và gán kết quả xử lý từ model
vào [views](structure-views.md), và cuối cùng là gửi phản hồi.


## Hành động (Actions) <span id="actions"></span>

Mỗi Controller đều chứa các *action* để user có thế tìm thấy, gửi yêu cầu tới ứng dụng để xử lý
. Mỗi bộ điều khiển có thể có nhiều hành động.

Ví dụ dưới mô tả Controller `post` cùng với 2 action là : `view` và `create`:

```php
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,
            ]);
        }
    }
}
```

Với action `view` (được định nghĩa bởi phương thức `actionView()`), dòng đầu dùng [Model](structure-models.md)
để tải dữ liệu dựa theo định danh ID; Nếu Model được tải thành công, thì sẽ được hiển thị qua
[view](structure-views.md) là  `view`. Còn không, ứng dụng sẽ thông báo ngoại lệ là không tìm thấy.

Với action `create` (được định nghĩa bởi phương thức `actionCreate()`), tương tự như vậy. Trước tiên
sẽ khởi tạo [Model](structure-models.md), Model sẽ thực hiện nhận dữ liệu và lưu thông tin. Nếu cả hai việc này thành công thì Controller
sẽ điều hướng trình duyệt tới View `view` cùng với định danh ID vừa được tạo bởi Model. Còn không, Controller sẽ gọi
View  `create` có chức năng hiển thị form nhập liệu.


## Bộ định tuyến (Routes) <span id="routes"></span>

Người dùng có thể tìm thấy các actions qua các bộ định tuyến gọi là *routes*. Mỗi Route là chuỗi bao gồm các thông tin:

* Một định danh của Module:  chỉ tồn tại nếu bộ điều khiển thuộc về thành phần [module](structure-modules.md);
* Một định danh của [Controller](#controller-ids): là một chuỗi xác định duy nhất của Controller trong ứng dụng
  (hoặc có thể là Module nếu Controller tương ứng là một Module);
* Một [Action ](#action-ids):là một chuỗi xác định duy nhất của Action trong ứng dụng.

Mỗi Route có định dạng như sau:

```
ControllerID/ActionID
```

hoặc có định dạng sau nếu Controller được gán như một Module:

```php
ModuleID/ControllerID/ActionID
```

Như vậy nếu user truy cập vào đường dẫn sau `https://hostname/index.php?r=site/index`, thì hành động `index` nằm trong bộ điều khiển `site`
sẽ được thực hiện. Để biết thêm thông tin về cách bộ định tuyến xác định các hành động, vui lòng tham khảo tại mục
[Routing và URL Generation](runtime-routing.md).


## Tạo Controller <span id="creating-controllers"></span>

Trong mỗi [[yii\web\Application|Ứng dụng Web]], Controllers cần được kế thừa từ class [[yii\web\Controller]] hoặc các lớp con của nó
. Tương tự trong [[yii\console\Application|Ứng dụng console]], Controllers cần được kế thừa từ class
[[yii\console\Controller]] hoặc các lớp con của nó. Đoạn code sau được định nghĩa trong Controller `site` :

```php
namespace app\controllers;

use yii\web\Controller;

class SiteController extends Controller
{
}
```


### Định danh Controller (Controller ID) <span id="controller-ids"></span>

Thông thường, Controller được thiết kế để xử lý các yêu cầu từ các nguồn tài nguyên cụ thể.
Vì lý do này, mỗi định danh của Controller thường là danh từ cập nhật tới kiểu tài nguyên được xử lý.
Ví dụ, sử dụng `article` như định danh của Controller nhằm lưu giữ các dữ liệu về bài viết.

Mặc định, Thông tin các định danh Controller nên chỉ chứa các ký tự : Các chữ cái viết thường, số,
dấu gạch dưới, dấu nối, và dấu gạch nghiêng phải.  Ví dụ, `article` và `post-comment` đều là những định danh đúng,
trong khi đó `article?`, `PostComment`, `admin\post` đều không hợp lý.

Các định danh Controller ID nên chứa tiền tố trong thư mục con. Ví dụ, `admin/article` xác định cho Controller `article`
nằm trong thư mục `admin` được dựa theo [[yii\base\Application::controllerNamespace|controller namespace]].
Các ký tự hợp lệ dùng cho các tiền tố của thư mục con bao gồm: Các chữ cái viết thường, số, dấu gạch, và dấu gạch nghiêng phải
, dấu gạch nghiêng phải được dùng để phân cách như các thư mục con (vd. `panels/admin`).


### Tên lớp Controller <span id="controller-class-naming"></span>

Tên lớp của các Controller được khởi tạo từ các định danh Controller theo các bước sau:

1. Chuyển ký tự đầu tiên trong mỗi từ cách nhau bởi dấu gạch nối thành ký tự hoa. Lưu ý rằng nếu các định danh Controller 
  có chứa dấu gạch chéo, thì quy tắc này chỉ được áp dụng ở phần sau dấu gạch chéo cuối cùng trong các định danh.
2. Xoá các dấu gạch nối và thay thế các dấu gạch chéo xuôi(/) thành dấu gạch chéo ngược (\).
3. Thêm hậu tố `Controller`.
4. Thêm [[yii\base\Application::controllerNamespace|controller namespace]].

Xem ví dụ sau, giả sử [[yii\base\Application::controllerNamespace|controller namespace]]
nhận giá trị mặc định là `app\controllers`:

* `article` thành `app\controllers\ArticleController`;
* `post-comment` thành `app\controllers\PostCommentController`;
* `admin/post-comment` thành `app\controllers\admin\PostCommentController`;
* `adminPanels/post-comment` thành `app\controllers\adminPanels\PostCommentController`.

Các lớp Controller cần được [tự động tải](concept-autoloading.md). Vì vậy, trong ví dụ trên,
lớp của Controller `article` cần được lưu vào file có [bí danh](concept-aliases.md)
là `@app/controllers/ArticleController.php`; trong khi đó `admin/post-comment` cần được lưu vào file
là `@app/controllers/admin/PostCommentController.php`.

> Lưu ý: Ở ví dụ với định danh Controller `admin/post-comment` hướng dẫn bạn đặt các Controller vào trong thư mục con
  của [[yii\base\Application::controllerNamespace|không gian tên Controller]]. Thông tin này khá là hữu ích
  mỗi khi bạn muốn quản lý các Controllers và các chuyên mục và bạn không muốn sử dụng thành phần [Modules](structure-modules.md).


### Controller Map <span id="controller-map"></span>

Bạn có thể cấu hình thông tin về mục  [[yii\base\Application::controllerMap|controller map]] để khắc phục những hạn chế về các định danh
và tên class của Controller được mô tả ở trên. Điều này khá hữu ích khi bạn muốn sử dụng
các Controller ở bên thứ ba và bạn không có quyền việc kiểm soát các class này.

Bạn có thể cấu hình [[yii\base\Application::controllerMap|controller map]] trong mục
[cấu hình ứng dụng](structure-applications.md#application-configurations). Ví dụ:

```php
[
    'controllerMap' => [
        // mô tả Controller "account" được sử dụng
        'account' => 'app\controllers\UserController',

        // mô tả về cấu hình Controller"article" dạng mảng 
        'article' => [
            'class' => 'app\controllers\PostController',
            'enableCsrfValidation' => false,
        ],
    ],
]
```


### Controller mặc định <span id="default-controller"></span>

Mỗi ứng dụng đều có một Controller mặc định được mô tả qua thuộc tính [[yii\base\Application::defaultRoute]].
Khi một yêu cầu không được mô tả cụ thể ở mục [route](#routes), thì route mặc định sẽ được gọi.
Chẳng hạn [[yii\web\Application|Web applications]], có giá trị là `'site'`, trong khi đó [[yii\console\Application|ứng dụng console]],
có route mặc định là `help`. Vì vậy, nếu truy cập vào URL sau `https://hostname/index.php`, thì Controller `site` sẽ được gọi và xử lý yêu cầu.

Bạn có thể thay đổi thông tin Controller mặc định tại mục [cấu hình ứng dung](structure-applications.md#application-configurations) như sau:

```php
[
    'defaultRoute' => 'main',
]
```


## Tạo Actions <span id="creating-actions"></span>

Tạo mới một Action khá là đơn giản, bằng chỉ việc định nghĩa trong lớp Controller cùng với tên *action phương thức*. Các phương thức của mỗi Action
đều có phạm vi *toàn cục* tên của phương thức được bắt đầu bằng từ `action`. Kết quả trả về của mỗi action sẽ tương ứng với
dữ liệu được gửi tới user. Đoạn mã sau sẽ định nghĩa hai action là, `index` và `hello-world`:

```php
namespace app\controllers;

use yii\web\Controller;

class SiteController extends Controller
{
    public function actionIndex()
    {
        return $this->render('index');
    }

    public function actionHelloWorld()
    {
        return 'Hello World';
    }
}
```


### Định danh của Action (Action ID) <span id="action-ids"></span>

Mỗi action được dùng cho nhiệm vụ với tài nguyên cụ thể. Vì lý do này, mỗi
action ID thường là một đông từ, như `view`, `update`, vv.

Mặc định, mỗi action ID chỉ nên chứa các chữ cái: Chữ thường, số,
dấu gạch dưới, và dấu gạch ngang. (Bạn cũng có thể sử dụng các dấu gạch ngang để nối các từ lại với nhau.) Ví dụ,
`view`, `update2`, và `comment-post` đều là những định danh hợp lệ, còn `view?` và `Update` là không hợp lệ.

Có hai cách để tạo mới các action: inline actions và standalone actions. Với inline action được định nghĩa
như những phương thức trong lớp Controller, trong khi đó standalone action là lớp được kế thừa từ lớp
[[yii\base\Action]] hoặc là lớp con. Nếu bạn không muốn tái sử dụng các action thì bạn có thể dùng inline actions
, cách này thường hay được sử dụng hơn. Trong khi đó các standalone actions thường được tạo để sử dụng
ở những Controllers khác nhau và được dùng như [thành phần mở rộng](structure-extensions.md).


### Inline Actions <span id="inline-actions"></span>

Inline actions được định nghĩa vào trong các phương thức như chúng ta đã mô tả trên.

Tên của phương thức thường đặt tên theo định danh của action và theo các bước sau:

1. Chuyển ký tự đầu tiên trong mỗi từ định danh của action thành ký tự in hoa.
2. Xoá dấu gạch nối.
3. Thêm tiền tố `action`.

Ví dụ, `index` thành `actionIndex`, và `hello-world` thành `actionHelloWorld`.

> Lưu ý: Việc đặt tên của các phương thức cần phải *cẩn thận*. Nếu bạn có phương thức là `ActionIndex`,
  thì phương thức của action sẽ không xác định, như vậy, khi có yêu cầu tới action `index`
  thì sẽ sinh ra lỗi. Cũng lưu ý rằng các phương thức này phải ở phạm vi public (toàn cục). Các phạm vi private (riêng) hoặc protected (bảo vệ)
  thì sẽ không được định nghĩa như một action.


Inline actions thường được hay sử dụng hơn bởi vì việc khởi tạo đơn giản hơn. Tuy nhiên,
nếu bạn muốn tái sử dụng action ở những vị trí khác, bạn có thể tham khảo thêm ở mục *standalone action*.


### Standalone Actions <span id="standalone-actions"></span>

Standalone actions được định nghĩa từ việc kế thừa từ class [[yii\base\Action]] hoặc các lớp con của nó.
Ví dụ, ở phiên bản Yii đã phát hành, các action [[yii\web\ViewAction]] và [[yii\web\ErrorAction]], đều là những
standalone actions.

Để sử dụng standalone action, bạn cần phải khai báo ở phần *liên kết các action* bằng việc ghi đè lên phương thức
[[yii\base\Controller::actions()]] ở lớp Controller như sau:

```php
public function actions()
{
    return [
        // khai báo action "error" bằng việc sử dụng tên class
        'error' => 'yii\web\ErrorAction',

        // khai báo action "view" bằng thông tin cấu hình dạng mảng
        'view' => [
            'class' => 'yii\web\ViewAction',
            'viewPrefix' => '',
        ],
    ];
}
```

Như vậy, phương thức `actions()` sẽ trả về một mảng và chứa các khoá của các định danh action và giá trị tương ứng
tên class hoặc thông tin [cấu hình](concept-configurations.md). Không giống như inline actions, action ID được dùng cho standalone
actions có thể chứa các ký tự tuỳ ý, miễn là chúng được khai báo trong phương thức `actions()`.

Để tạo các class standalone action, bạn nên kế thừa từ lớp [[yii\base\Action]] hoặc lớp con của nó, và hiện thực
phương thức là `run()`. Vài trò của phương thức `run()` tương tự như một phương thức của action. Chẳng hạn,

```php
<?php
namespace app\components;

use yii\base\Action;

class HelloWorldAction extends Action
{
    public function run()
    {
        return "Hello World";
    }
}
```


### Kết quả trả về của Action <span id="action-results"></span>

Kết quả trả về của phương thức action hoặc phương thức `run()` của standalone action khá quan trọng. Nó là
kết quả tương ứng của từng action.

Giá trị trả về là đối tượng [phản hồi](runtime-responses.md) được gửi tới user như những phản hồi.

* Chẳng hạn với [[yii\web\Application|Ứng dụng Web]], kết quả trả về bao gồm dữ liệu được gán vào thuộc tính
  [[yii\web\Response::data]] và chuyển sang dữ liệu là string chuyển tới nội dung phản hồi kết quả.
* Với [[yii\console\Application|ứng dụng console]], kết quả trả về là số nguyên tương ứng với thuộc tính
  [[yii\console\Response::exitStatus|exit status]] của mỗi lần thực thi lệnh.

Ở ví dụ dưới, action sẽ trả về là chuỗi dữ liệu và được xử lý như nội dung phản hồi tới user
. Ví dụ dưới chỉ cách các action điều hướng tới trình duyệt một URL 
bằng việc gửi một đối tượng phản hồi (vì phương thức [[yii\web\Controller::redirect()|redirect()]] sẽ trả về
một đối tượng):

```php
public function actionForward()
{
    // điều hướng tới URL https://example.com
    return $this->redirect('https://example.com');
}
```


### Các tham số của Action <span id="action-parameters"></span>

Các phương thức dành cho inline action và phương thức `run()` cho standalone actions có thể nhận các tham số,
được gọi là *các tham số action*. Giá trị nhận được từ các yêu cầu. Với [[yii\web\Application|Ứng dụng Web]],
giá trị của các tham số được nhận từ biến `$_GET` sử dụng các tham số như các khoá;
với [[yii\console\Application|ứng dụng console]], các tham số sẽ tương ứng với các đối số dòng lệnh.

Trong ví dụ sau, action `view`  (là một inline action) được khai báo hai tham số là: `$id` và `$version`.

```php
namespace app\controllers;

use yii\web\Controller;

class PostController extends Controller
{
    public function actionView($id, $version = null)
    {
        // ...
    }
}
```

Các tham số cho action sẽ được dùng như sau và tương ứng với các yêu cầu khác nhau:

* `https://hostname/index.php?r=post/view&id=123`: biến `$id` sẽ nhận giá trị là
  `'123'`,  trong khi đó tham số `$version` nhận giá trị null vì không có đối số `version` được truyền lên.
* `https://hostname/index.php?r=post/view&id=123&version=2`: biến `$id` và `$version` sẽ nhận giá trị tương ứng là
   `'123'` và `'2'`.
* `https://hostname/index.php?r=post/view`: ngoại lệ [[yii\web\BadRequestHttpException]] sẽ được gửi ra
  vì tham số `$id` không được gửi lên.
* `https://hostname/index.php?r=post/view&id[]=123`: xảy ra ngoại lệ [[yii\web\BadRequestHttpException]] lý do vì
  tham số `$id` nhận dữ liệu là một mảng do vậy không hợp lệ `['123']`.

Nếu bạn muốn tham số của action nhận dữ liệu là một mảng, bạn nên khai báo biên là `array`, như sau:

```php
public function actionView(array $id, $version = null)
{
    // ...
}
```

Nếu yêu cầu là `https://hostname/index.php?r=post/view&id[]=123`, thì tham số `$id` sẽ nhận giá trị là
 `['123']`. Nếu yêu cầu là `https://hostname/index.php?r=post/view&id=123`, tham số `$id` sẽ chỉ nhận
các giá trị trong mảng là giống nhau bởi vì giá trị `'123'` không là mảng và sẽ tự động chuyển vào mảng.

Ở ví dụ trên sẽ hướng dẫn các tham số trong mỗi action hoạt động trong ứng dụng Web. Với ứng dụng console,
vui lòng tham khảo tại mục [Console Commands](tutorial-console.md) để biết thêm thông tin.


### Action mặc định <span id="default-action"></span>

Mỗi controller đều có các action mặc định và đợc mô tả ở thuộc tính [[yii\base\Controller::defaultAction]].
Mỗi khi [route](#routes) chỉ nhận giá trị là định danh của controller, router sẽ tự hiểu rằng action mặc định
của controller sẽ được gọi.

Mặc định, action mặc định sẽ là `index`. nếu bạn muốn thay đổi giá trị này, cách đơn giản nhất là ghi đè thuộc tính
trong lớp controller, như sau:

```php
namespace app\controllers;

use yii\web\Controller;

class SiteController extends Controller
{
    public $defaultAction = 'home';

    public function actionHome()
    {
        return $this->render('home');
    }
}
```


## Chu trình Controller <span id="controller-lifecycle"></span>

Khi xử lý yêu cầu, [ứng dụng](structure-applications.md) sẽ khởi tạo controller
dựa theo các yêu cầu tại [route](#routes). Controller sẽ được xử lý qua các chu trình sau để xử lý các yêu cầu:

1. Phương thức [[yii\base\Controller::init()]] sẽ được gọi sau khi controller được khởi tạo và thiết lập các cấu hình.
2. Controller sẽ tạo đối tượng action dựa trên các yêu cầu qua các định danh của action:
   * Nếu định danh của action không được chỉ rõ , thì  [[yii\base\Controller::defaultAction|action mặc định]] sẽ được sử dụng.
   * Nếu định danh của action được tìm thấy trong phương thức [[yii\base\Controller::actions()|action map]], thì một standalone action
     sẽ được khởi tạo;
   * Nếu định danh của action được tìm thấy và khớp với phương thức của action, thì một inline action sẽ được;
   * Mặt khác hệ thống sẽ gửi ngoại lê [[yii\base\InvalidRouteException]] ra.
3. Controller sẽ lần lượt được gọi tại phương thức `beforeAction()` trong ứng dụng, trong module (nếu controller
   thuộc một module), và trong controller.
   * Nếu một trong các phương thức không đợc gọi, các phần chưa được gọi trong phương thức `beforeAction()` sẽ được bỏ qua
     và việc thực hiện action sẽ bị huỷ bỏ.
   * Mặc định, mỗi phương thức `beforeAction()` sẽ được gán vào sự kiện `beforeAction` tới các action mà bạn cần xử lý.
4. Controller thực hiện chạy action.
   * Các tham số của action sẽ được phân tích và gán từ các yêu cầu xử lý.
5. Controller sẽ thực hiện tuần tự gọi phương thức `afterAction()` trong Controller, module (nếu Controller
   là module), và trong ứng dụng.
   * Mặc định, mỗi phương thức `afterAction()` sẽ gọi tới một sự kiện `afterAction` tới các action mà bạn cần xử lý.
6. Ứng dụng sẽ nhận kết quả từ các action và chuyển tới thành phần [response](runtime-responses.md).


## Thực hành <span id="best-practices"></span>

Với mỗi ứng dụng được thiết kế tốt, thì Controllers thường rất gọn nhẹ, mỗi action chỉ chứa khá ít dòng code.
Nếu Controller trong ứng dụng của bản khá phức tạp, thì bạn nên cấu trúc lại và chuyển sang một lớp khác.

Sau đây gợi ý vài thủ thuật. Controllers

* có thể truy cập dữ liệu từ các [request](runtime-requests.md);
* gọi các phương thức từ [models](structure-models.md) và các thành phần khác cùng với dữ liệu được gửi;
* dùng thành phần [views](structure-views.md) để gửi phản hồi;
* KHÔNG NÊN xử lý dữ liệu - nên xử lý ở tầng [model](structure-models.md);
* không nên nhúng mã HTML vào hoặc đoạn mã khác - mã này nên nhũng ở thành phần [views](structure-views.md).
