2 位追蹤者

控制器

控制器是 MVC 架構的一部分。它們是從 yii\base\Controller 擴展而來的類別的物件,負責處理請求和產生回應。特別是,在從 應用程式 接管控制權後,控制器將分析傳入的請求資料,將其傳遞給 模型,將模型結果注入 視圖,最後產生傳出的回應。

動作

控制器由動作組成,動作是最終使用者可以定址和請求執行的最基本單元。一個控制器可以有一個或多個動作。

以下範例顯示了一個具有兩個動作的 post 控制器:viewcreate

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 載入 模型;如果模型載入成功,它將使用名為 view視圖 顯示它。否則,它將拋出例外。

create 動作(由 actionCreate() 方法定義)中,程式碼類似。它首先嘗試使用請求資料填充 模型 的新實例並儲存模型。如果兩者都成功,它將將瀏覽器重新導向到具有新建立模型 ID 的 view 動作。否則,它將顯示 create 視圖,使用者可以透過該視圖提供所需的輸入。

路由

最終使用者透過所謂的路由來定址動作。路由是一個字串,由以下部分組成

  • 模組 ID:僅當控制器屬於非應用程式 模組 時才存在;
  • 控制器 ID:一個字串,用於在同一應用程式(或同一模組,如果控制器屬於模組)中的所有控制器中唯一識別控制器;
  • 動作 ID:一個字串,用於在同一控制器中的所有動作中唯一識別動作。

路由採用以下格式

ControllerID/ActionID

如果控制器屬於模組,則採用以下格式

ModuleID/ControllerID/ActionID

因此,如果使用者使用 URL https://hostname/index.php?r=site/index 請求,則將執行 site 控制器中的 index 動作。有關路由如何解析為動作的更多詳細資訊,請參閱 路由與 URL 建立 章節。

建立控制器

Web 應用程式 中,控制器應從 yii\web\Controller 或其子類別擴展。同樣,在 主控台應用程式 中,控制器應從 yii\console\Controller 或其子類別擴展。以下程式碼定義了一個 site 控制器

namespace app\controllers;

use yii\web\Controller;

class SiteController extends Controller
{
}

控制器 ID

通常,控制器旨在處理關於特定類型資源的請求。因此,控制器 ID 通常是名詞,指的是它們正在處理的資源類型。例如,您可以將 article 用作處理文章資料的控制器的 ID。

預設情況下,控制器 ID 應僅包含以下字元:小寫英文字母、數字、底線、連字號和正斜線。例如,articlepost-comment 都是有效的控制器 ID,而 article?PostCommentadmin\post 則不是。

控制器 ID 也可能包含子目錄前綴。例如,admin/article 代表 控制器命名空間admin 子目錄中的 article 控制器。子目錄前綴的有效字元包括:大小寫英文字母、數字、底線和正斜線,其中正斜線用作多層子目錄的分隔符(例如 panels/admin)。

控制器類別命名

控制器類別名稱可以根據以下程序從控制器 ID 派生

  1. 將每個以連字號分隔的單字的第一個字母轉換為大寫。請注意,如果控制器 ID 包含斜線,則此規則僅適用於 ID 中最後一個斜線之後的部分。
  2. 移除連字號並將任何正斜線替換為反斜線。
  3. 附加字尾 Controller
  4. 前置 控制器命名空間

以下是一些範例,假設 控制器命名空間 採用預設值 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

控制器類別必須是 可自動載入的。因此,在上述範例中,article 控制器類別應儲存在其 別名@app/controllers/ArticleController.php 的檔案中;而 admin/post-comment 控制器應位於 @app/controllers/admin/PostCommentController.php 中。

資訊:最後一個範例 admin/post-comment 顯示了如何將控制器放在 控制器命名空間 的子目錄下。當您想要將控制器組織成多個類別,並且不想使用 模組 時,這很有用。

控制器地圖

您可以設定 控制器地圖,以克服上述控制器 ID 和類別名稱的限制。當您使用第三方控制器並且無法控制其類別名稱時,這主要很有用。

您可以在 應用程式設定 中設定 控制器地圖。例如

[
    'controllerMap' => [
        // declares "account" controller using a class name
        'account' => 'app\controllers\UserController',

        // declares "article" controller using a configuration array
        'article' => [
            'class' => 'app\controllers\PostController',
            'enableCsrfValidation' => false,
        ],
    ],
]

預設控制器

每個應用程式都有一個預設控制器,透過 yii\base\Application::$defaultRoute 屬性指定。當請求未指定 路由 時,將使用此屬性指定的路由。對於 Web 應用程式,其值為 'site',而對於 主控台應用程式,其值為 help。因此,如果 URL 是 https://hostname/index.php,則 site 控制器將處理請求。

您可以使用以下 應用程式設定 變更預設控制器

[
    'defaultRoute' => 'main',
]

建立動作

建立動作可以像在控制器類別中定義所謂的動作方法一樣簡單。動作方法是一個public 方法,其名稱以單字 action 開頭。動作方法的傳回值表示要傳送給最終使用者的回應資料。以下程式碼定義了兩個動作,indexhello-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 通常是動詞,例如 viewupdate 等。

預設情況下,動作 ID 應僅包含以下字元:小寫英文字母、數字、底線和連字號(您可以使用連字號分隔單字)。例如,viewupdate2comment-post 都是有效的動作 ID,而 view?Update 則不是。

您可以使用兩種方式建立動作:內聯動作和獨立動作。內聯動作定義為控制器類別中的方法,而獨立動作是一個擴展 yii\base\Action 或其子類別的類別。內聯動作的建立工作量較少,如果您不打算重複使用這些動作,通常是首選。另一方面,建立獨立動作主要是為了在不同的控制器中使用或作為 擴充套件 重新發布。

內聯動作

內聯動作是指以動作方法定義的動作,正如我們剛才描述的那樣。

動作方法的名稱是根據以下程序從動作 ID 派生的

  1. 將動作 ID 中每個單字的第一個字母轉換為大寫。
  2. 移除連字號。
  3. 前置字首 action

例如,index 變成 actionIndex,而 hello-world 變成 actionHelloWorld

注意:動作方法的名稱是區分大小寫的。如果您有一個名為 ActionIndex 的方法,它將不會被視為動作方法,因此,對 index 動作的請求將導致例外。另請注意,動作方法必須是 public 的。private 或 protected 方法不會定義內聯動作。

內聯動作是最常定義的動作,因為它們的建立工作量很少。但是,如果您計劃在不同位置重複使用相同的動作,或者如果您想要重新發布動作,則應考慮將其定義為獨立動作

獨立動作

獨立動作是根據擴展 yii\base\Action 或其子類別的動作類別定義的。例如,在 Yii 發行版中,有 yii\web\ViewActionyii\web\ErrorAction,它們都是獨立動作。

若要使用獨立動作,您應該透過覆寫控制器類別中的 yii\base\Controller::actions() 方法,在動作地圖中宣告它,如下所示

public function actions()
{
    return [
        // declares "error" action using a class name
        'error' => 'yii\web\ErrorAction',

        // declares "view" action using a configuration array
        'view' => [
            'class' => 'yii\web\ViewAction',
            'viewPrefix' => '',
        ],
    ];
}

如您所見,actions() 方法應傳回一個陣列,其鍵是動作 ID,值是對應的動作類別名稱或 設定。與內聯動作不同,獨立動作的動作 ID 可以包含任意字元,只要它們在 actions() 方法中宣告即可。

若要建立獨立動作類別,您應該擴展 yii\base\Action 或子類別,並實作一個名為 run() 的 public 方法。run() 方法的角色類似於動作方法。例如,

<?php
namespace app\components;

use yii\base\Action;

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

動作結果

動作方法或獨立動作的 run() 方法的傳回值非常重要。它代表對應動作的結果。

傳回值可以是 回應 物件,該物件將作為回應傳送給最終使用者。

在上面顯示的範例中,動作結果都是字串,這些字串將被視為要傳送給最終使用者的回應主體。以下範例顯示了動作如何透過傳回回應物件將使用者瀏覽器重新導向到新的 URL(因為 redirect() 方法傳回回應物件)

public function actionForward()
{
    // redirect the user browser to https://example.com
    return $this->redirect('https://example.com');
}

動作參數

內聯動作的動作方法和獨立動作的 run() 方法可以採用參數,稱為動作參數。它們的值是從請求中取得的。對於 Web 應用程式,每個動作參數的值都是使用參數名稱作為鍵從 $_GET 中檢索的;對於 主控台應用程式,它們對應於命令列引數。

在以下範例中,view 動作(內聯動作)宣告了兩個參數:$id$version

namespace app\controllers;

use yii\web\Controller;

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

對於不同的請求,動作參數將按如下方式填充

  • https://hostname/index.php?r=post/view&id=123$id 參數將填充值 '123',而 $version 仍然是 null,因為沒有 version 查詢參數。
  • https://hostname/index.php?r=post/view&id=123&version=2$id$version 參數將分別填充 '123''2'
  • https://hostname/index.php?r=post/view:將拋出 yii\web\BadRequestHttpException 例外,因為請求中未提供必要的 $id 參數。
  • https://hostname/index.php?r=post/view&id[]=123:將拋出 yii\web\BadRequestHttpException 例外,因為 $id 參數正在接收意外的陣列值 ['123']

如果您希望動作參數接受陣列值,則應使用 array 類型提示它,如下所示

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

現在,如果請求是 https://hostname/index.php?r=post/view&id[]=123,則 $id 參數將採用 ['123'] 的值。如果請求是 https://hostname/index.php?r=post/view&id=123,則 $id 參數仍將收到相同的陣列值,因為純量值 '123' 將自動轉換為陣列。

以上範例主要顯示了動作參數如何適用於 Web 應用程式。對於主控台應用程式,請參閱 命令列指令 章節以取得更多詳細資訊。

預設動作

每個控制器都有一個預設動作,透過 yii\base\Controller::$defaultAction 屬性指定。當 路由 僅包含控制器 ID 時,這表示請求的是指定控制器的預設動作。

預設情況下,預設動作設定為 index。如果您想要變更預設值,只需在控制器類別中覆寫此屬性,如下所示

namespace app\controllers;

use yii\web\Controller;

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

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

控制器生命週期

在處理請求時,應用程式 將根據請求的 路由 建立控制器。然後,控制器將經歷以下生命週期以完成請求

  1. 在建立和設定控制器後,將呼叫 yii\base\Controller::init() 方法。
  2. 控制器根據請求的動作 ID 建立動作物件
  3. 控制器依序呼叫應用程式、模組(如果控制器屬於模組)和控制器的 beforeAction() 方法。
    • 如果其中一個呼叫傳回 false,則將跳過其餘未呼叫的 beforeAction() 方法,並且將取消動作執行。
    • 預設情況下,每個 beforeAction() 方法呼叫都會觸發 beforeAction 事件,您可以將處理常式附加到該事件。
  4. 控制器執行動作。
    • 將分析動作參數並從請求資料中填充。
  5. 控制器依序呼叫控制器、模組(如果控制器屬於模組)和應用程式的 afterAction() 方法。
    • 預設情況下,每個 afterAction() 方法呼叫都會觸發 afterAction 事件,您可以將處理常式附加到該事件。
  6. 應用程式將取得動作結果並將其指派給 回應

最佳實踐

在設計良好的應用程式中,控制器通常非常精簡,每個動作僅包含幾行程式碼。如果您的控制器相當複雜,通常表示您應該重構它並將一些程式碼移至其他類別。

以下是一些具體的最佳實踐。控制器

  • 可以存取 請求 資料;
  • 可以使用請求資料呼叫 模型 和其他服務組件的方法;
  • 可以使用 視圖 來組成回應;
  • 不應處理請求資料 - 這應在 模型層 中完成;
  • 應避免嵌入 HTML 或其他呈現程式碼 - 這最好在 視圖 中完成。

發現錯字或您認為此頁面需要改進?
在 github 上編輯 !