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',
]

创建操作

创建操作就像在控制器类中定义所谓的操作方法一样简单。操作方法是一个公共方法,其名称以 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 变为 actionIndexhello-world 变为 actionHelloWorld

注意:操作方法的名称是区分大小写的。如果您有一个名为 ActionIndex 的方法,它将不被视为操作方法,因此,对 index 操作的请求将导致异常。另请注意,操作方法必须是公共的。私有或受保护的方法不会定义内联操作。

内联操作是最常定义的操作,因为它们创建起来非常简单。但是,如果您计划在不同的地方重用相同的操作,或者您想重新分发操作,您应该考虑将其定义为独立操作

独立操作

独立操作是根据扩展 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() 的公共方法。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 上编辑它 !