Yii 2 控制器(Controller)完全指南:路由、Action 与请求处理生命周期
2026/9/24 14:39:56 网站建设 项目流程
  • 后端
  • Web框架

【免费下载链接】yii2

Yii 2: The Fast, Secure and Professional PHP Framework

项目地址:https://gitcode.com/gh_mirrors/yi/yii2
点击查看免费下载

本篇指南以 Yii 2 官方指南的控制器章节为骨架,系统讲解控制器在 MVC 架构 中的职责、路由(Route)的解析规则、Controller 与 Action 的创建规范、Action 参数绑定机制以及控制器完整生命周期。读完本文,你将能够正确命名与组织控制器和 Action、通过 controllerMap 接入第三方控制器、按需选用 Inline/Standalone Action,并深入理解请求从进入应用到产出响应的每一步内部机制。文章中的代码示例可直接复制运行,源码级证据均来自本仓库的 framework 目录。

控制器在 MVC 中的角色

控制器是 MVC 设计模式 的一部分。在 Yii 2 中,控制器是继承自yii\base\Controller的类对象(见 framework/base/Controller.php),其职责是处理请求并生成响应

具体来说,当控制器从应用接过控制权后,会依次完成以下工作:

  1. 分析传入的请求数据;
  2. 将数据交给模型处理业务逻辑;
  3. 把模型的处理结果注入视图;
  4. 最终生成对外响应。

也就是说,控制器是模型与视图之间的"调度中枢":它自己不做业务计算,也不直接输出 HTML,而是协调两者完成一次完整的请求-响应闭环。

Actions:用户可寻址的最小执行单元

控制器由Action(动作)组成。Action 是终端用户可以直接寻址并要求执行的最小单元,一个控制器可以包含一个或多个 Action。

下面的示例定义了一个post控制器,包含viewcreate两个 Action:

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, ]); } } }
  • viewAction(由actionView()方法定义):先按请求中的模型 ID 加载模型;加载成功则通过名为view的视图展示数据;否则抛出NotFoundHttpException(404)异常。
  • createAction(由actionCreate()方法定义):先尝试用请求数据填充一个新的模型实例并保存;若保存成功,则将浏览器重定向到viewAction 并携带新模型的 ID;否则渲染create视图,让用户填写输入表单。

路由(Routes):如何寻址到 Action

终端用户通过所谓的route(路由)来寻址 Action。一个路由字符串由以下部分组成:

  • 模块 ID(module ID):仅当控制器属于某个非应用级模块时才存在;
  • 控制器 ID:在同级应用(或同一模块)内唯一标识一个控制器的字符串;
  • Action ID:在同一控制器内唯一标识一个 Action 的字符串。

路由的基本格式为:

ControllerID/ActionID

若控制器属于某个模块,则格式为:

ModuleID/ControllerID/ActionID

例如,当用户请求https://hostname/index.php?r=site/index时,site控制器中的indexAction 将被执行。关于路由如何进一步被解析为具体 Action 的完整细节,请参考路由与 URL 生成章节。

从源码层面看,路由到控制器的解析过程在yii\base\Module::createController()(见 framework/base/Module.php)中实现,其算法依次为:

  1. 路由为空时使用defaultRoute
  2. 路由首段命中controllerMap时,按其中的配置创建控制器;
  3. 路由首段是已声明的模块 ID 时,将剩余部分交给该模块的createController()递归解析;
  4. 否则在controllerNamespace下尝试abc\DefControllerabc\def\XyzController两种类名形态。

最终Module::runAction()(见 framework/base/Module.php)拿到控制器与剩余 Action ID 后,调用Controller::runAction()执行。

创建控制器

在 Web 应用 中,控制器应继承yii\web\Controller或其子类;同理,在 console 应用 中,控制器应继承yii\console\Controller或其子类。下面的代码定义了一个site控制器:

namespace app\controllers; use yii\web\Controller; class SiteController extends Controller { }

控制器 ID(Controller ID)

通常,一个控制器专为处理某一类特定资源的请求而设计,因此控制器 ID 常采用名词,指代它所处理的资源类型。例如,用article作为处理文章数据的控制器 ID。

默认情况下,控制器 ID 只能包含以下字符:小写英文字母、数字、下划线、连字符(-)和正斜杠(/)。例如articlepost-comment都是合法 ID,而article?PostCommentadmin\post均不合法。

控制器 ID 还可以包含子目录前缀。例如admin/article表示在controllerNamespace(控制器命名空间)下的admin子目录中名为article的控制器。子目录前缀允许的字符包括:大小写英文字母、数字、下划线和正斜杠,其中正斜杠用于分隔多级子目录(例如panels/admin)。在源码中,这一约束由Module::createControllerByID()中的isIncorrectClassNameOrPrefix()检查保证(见 framework/base/Module.php)。

控制器类命名(Controller Class Naming)

控制器类名按以下步骤从控制器 ID 推导:

  1. 将 ID 中每个以连字符分隔的单词首字母转为大写;注意若 ID 包含斜杠,该规则只作用于最后一个斜杠之后的部分;
  2. 删除连字符,并把正斜杠替换为反斜杠(命名空间分隔符);
  3. 追加后缀Controller
  4. 前面加上controllerNamespace

假设controllerNamespace取默认值app\controllers(该默认值定义于 framework/base/Application.php),则:

控制器 ID推导出的类名
articleapp\controllers\ArticleController
post-commentapp\controllers\PostCommentController
admin/post-commentapp\controllers\admin\PostCommentController
adminPanels/post-commentapp\controllers\adminPanels\PostCommentController

控制器类必须可自动加载。因此在上述示例中,article控制器类应存放在别名@app/controllers/ArticleController.php对应的文件里;admin/post-comment则应存放在@app/controllers/admin/PostCommentController.php

提示:admin/post-comment这个例子展示了如何把控制器放进controllerNamespace的子目录。当你想按分类组织控制器、又不想引入模块机制时,这个用法非常实用。

在源码实现层面,createControllerByID()使用正则%-([a-z0-9_])%i配合ucfirst完成"连字符转大写"步骤,最终拼接出className . 'Controller'并校验类是否存在、是否继承自yii\base\Controller(见 framework/base/Module.php)。

Controller Map:突破命名约束

可以通过配置controllerMap(控制器映射)来克服上述控制器 ID 与类名之间的推导约束。当你使用第三方控制器、无法控制其类名时,这一机制尤其有用。

在应用配置中配置controllerMap即可,例如:

[ 'controllerMap' => [ // 用类名声明 "account" 控制器 'account' => 'app\controllers\UserController', // 用配置数组声明 "article" 控制器 'article' => [ 'class' => 'app\controllers\PostController', 'enableCsrfValidation' => false, ], ], ]

这里的值可以是纯类名,也可以是标准配置数组。配置数组形式允许你顺带设置控制器属性——例如上面例子中把article控制器的enableCsrfValidation关闭(该属性定义于 framework/web/Controller.php,详见下文 CSRF 小节)。在源码中,controllerMapyii\base\Module的公开属性(见 framework/base/Module.php),并在createController()中优先于模块和默认类名推导被检查(见 framework/base/Module.php)。

默认控制器(Default Controller)

每个应用都有一个通过defaultRoute属性指定的默认控制器。当请求没有显式指定路由时,将使用该属性指定的路由。

  • Web 应用的默认值是'site'(见 framework/web/Application.php);
  • console 应用的默认值是'help'(见 framework/console/Application.php)。

因此,当访问https://hostname/index.php时,site控制器将处理该请求。可以在应用配置中修改默认控制器:

[ 'defaultRoute' => 'main', ]

创建 Actions

创建 Action 非常简单:在控制器类中定义所谓的action 方法即可。Action 方法是一个action开头的public方法,其返回值代表要发送给终端用户的响应数据。下面的代码定义了indexhello-world两个 Action:

namespace app\controllers; use yii\web\Controller; class SiteController extends Controller { public function actionIndex() { return $this->render('index'); } public function actionHelloWorld() { return 'Hello World'; } }

Action ID(Action ID)

一个 Action 通常针对资源的某一种特定操作而设计,因此 Action ID 多为动词,例如viewupdate等。

默认情况下,Action ID 只能包含:小写英文字母、数字、下划线和连字符(连字符用于连接单词)。例如viewupdate2comment-post都合法,而view?Update不合法。

创建 Action 有两种方式:Inline Actions(内联动作)Standalone Actions(独立动作)。Inline Action 以控制器类中的方法形式定义;Standalone Action 则是继承yii\base\Action或其子类的类。如果你不打算复用该 Action,Inline 方式更省力、也最常用;而 Standalone Action 主要用于在不同的控制器中复用,或以扩展形式对外分发。

Inline Actions(内联动作)

Inline Action 即上面描述的以方法形式定义的 Action。方法名按以下步骤从 Action ID 推导:

  1. 将 ID 中每个单词的首字母转为大写;
  2. 删除连字符;
  3. 加上前缀action

例如,indexactionIndexhello-worldactionHelloWorld

注意:Action 方法名是区分大小写的。如果你定义了ActionIndex这样的方法,它不会被当作 Action 方法,此时请求indexAction 将抛出异常。另外,Action 方法必须是public的,privateprotected方法不会定义出 Inline Action。

这一点在源码中有严格对应:Controller::createAction()首先用正则/^(?:[a-z0-9_]+-)*[a-z0-9_]+$/校验 Action ID,然后通过ucwordsstr_replace生成方法名,并用ReflectionMethod检查该方法是否为 public 且名称完全一致,只有全部通过才会创建InlineAction(见 framework/base/Controller.php)。InlineAction本身只记录控制器与方法名,运行时通过call_user_func_array调用(见 framework/base/InlineAction.php)。

Standalone Actions(独立动作)

Standalone Action 通过继承yii\base\Action或其子类的 Action 类来定义。在 Yii 发行版中,yii\web\ViewActionyii\web\ErrorAction都是 Standalone Action 的典型实例。

要使用 Standalone Action,需要在控制器类中重写actions()方法,在action map(动作映射)里声明它:

public function actions() { return [ // 用类名声明 "error" 动作 'error' => 'yii\web\ErrorAction', // 用配置数组声明 "view" 动作 'view' => [ 'class' => 'yii\web\ViewAction', 'viewPrefix' => '', ], ]; }

可以看到,actions()返回一个数组,键为 Action ID,值为对应的 Action 类名或配置数组。与 Inline Action 不同,Standalone Action 的 ID可以包含任意字符,只要它在actions()中声明即可。基类中actions()的默认实现返回空数组(见 framework/base/Controller.php),createAction()会优先在 action map 中查找 ID,命中则通过Yii::createObject()创建(见 framework/base/Controller.php)。

要创建 Standalone Action 类,应继承yii\base\Action或其子类,并实现一个名为run()的公开方法。run()的作用与 Action 方法类似。例如:

<?php namespace app\components; use yii\base\Action; class HelloWorldAction extends Action { public function run() { return "Hello World"; } }

Action::runWithParams()会校验run()方法是否存在,然后委托控制器完成参数绑定后再调用(见 framework/base/Action.php)。

Action 的返回值(Action Results)

Action 方法或 Standalone Action 的run()方法的返回值至关重要,它代表了该 Action 的执行结果。

返回值可以是一个响应对象,该对象将被作为响应直接发送给终端用户:

  • 对于 Web 应用,返回值也可以是任意数据,这些数据会被赋值给yii\web\Response::$data,再进一步转换为表示响应体的字符串;
  • 对于 console 应用,返回值还可以是一个整数,代表命令执行的退出状态码。

前面示例中的返回值都是字符串,会被当作发送给用户的响应体。下面的示例展示了一个 Action 如何通过返回响应对象(因为redirect()方法返回的就是响应对象)将用户浏览器重定向到新 URL:

public function actionForward() { // 将用户浏览器重定向到 https://example.com return $this->redirect('https://example.com'); }

redirect()方法定义于 framework/web/Controller.php,是Response::redirect()的快捷方式,支持字符串 URL、路径别名以及['route', 'param' => value]数组三种形式,默认使用 302 状态码。同类的便捷方法还有goHome()goBack()refresh()以及用于返回 JSON/XML 响应的asJson()/asXml()(见 framework/web/Controller.php)。

Action 参数(Action Parameters)

Inline Action 的方法和 Standalone Action 的run()方法都可以接收参数,这些参数被称为action 参数,其值来自请求。对于 Web 应用,每个 Action 参数的值以参数名作为键从$_GET中获取;对于 console 应用,则对应命令行参数。

下面的示例中,viewAction(Inline Action)声明了两个参数$id$version

namespace app\controllers; use yii\web\Controller; class PostController extends Controller { public function actionView($id, $version = null) { // ... } }

不同请求下 Action 参数的填充结果如下:

请求 URL参数填充结果
https://hostname/index.php?r=post/view&id=123$id'123'$version因没有version参数仍为null
https://hostname/index.php?r=post/view&id=123&version=2$id$version分别填充为'123''2'
https://hostname/index.php?r=post/view因必需的$id参数缺失,抛出yii\web\BadRequestHttpException
https://hostname/index.php?r=post/view&id[]=123$id收到意外的数组值['123'],抛出yii\web\BadRequestHttpException

如果你希望 Action 参数接受数组值,可以为其加上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 应用中 Action 参数的工作方式。console 应用的参数机制请参考控制台命令章节。

从源码看,参数绑定由yii\web\Controller::bindActionParams()实现(见 framework/web/Controller.php):它通过反射遍历方法参数,对每个参数依次尝试"从请求中取值 → 按类型校验/过滤 → 取默认值 → 记为缺失"。当参数带有array类型声明时,任何输入都会被强制转换为数组(见filterSingleTypeActionParam(),framework/web/Controller.php);若参数声明了intfloatbool等标量类型,则会通过filter_var进行校验与转换(见 framework/web/Controller.php);缺失的必需参数会汇总后统一抛出BadRequestHttpException。此外,从 2.0.36 开始,未在请求中出现的、类型为类/接口的参数还会走依赖注入绑定逻辑(bindInjectedParams(),见 framework/base/Controller.php),这是从源码结构中可以确认的扩展能力。

默认 Action(Default Action)

每个控制器都有一个通过defaultAction属性指定的默认 Action。当路由只包含控制器 ID 时,即表示请求该控制器的默认 Action。

默认情况下,默认 Action 为index。如果想改变默认值,直接在控制器类中重写该属性即可(该属性定义于 framework/base/Controller.php):

namespace app\controllers; use yii\web\Controller; class SiteController extends Controller { public $defaultAction = 'home'; public function actionHome() { return $this->render('home'); } }

控制器生命周期(Controller Lifecycle)

在处理请求时,应用会根据请求的路由创建控制器实例,然后控制器经历以下生命周期来完成请求:

  1. 初始化:控制器创建并完成配置后,调用Controller::init()方法。基类中init()负责将requestresponse属性解析为对应的应用组件实例(见 framework/base/Controller.php)。
  2. 创建 Action 对象:控制器根据请求的 Action ID 创建动作对象:
    • 若 Action ID 未指定,使用默认 Action ID(defaultAction);
    • 若 Action ID 在actions()返回的 action map 中命中,则创建 Standalone Action;
    • 若 Action ID 与某个 action 方法匹配,则创建 Inline Action;
    • 否则抛出yii\base\InvalidRouteException异常。
  3. beforeAction 阶段:控制器依次调用应用模块(若控制器属于某模块)、控制器自身beforeAction()方法:
    • 若其中一次调用返回false,则剩余的beforeAction()不再调用,Action 执行被取消;
    • 默认情况下,每次beforeAction()调用都会触发一个beforeAction事件,你可以在事件上挂接处理器。在基类实现中,该事件通过ActionEvent携带动作信息,并以其isValid属性决定是否继续(见 framework/base/Controller.php)。
  4. 运行 Action:控制器执行 Action。此时 Action 参数会从请求数据中被解析并填充(即上文bindActionParams()的绑定过程)。
  5. afterAction 阶段:控制器依次调用控制器自身模块(若属于模块)、应用afterAction()方法:
    • 默认情况下,每次afterAction()调用都会触发一个afterAction事件。基类实现中该事件的result属性会被作为最终返回值传递(见 framework/base/Controller.php)。
  6. 生成响应:应用接收 Action 的执行结果,并将其赋值给响应组件。

上述编排逻辑的源码实现在Controller::runAction()中(见 framework/base/Controller.php):它先解析 Action,再沿"模块链 → 控制器"方向调用beforeAction(),成功则执行$action->runWithParams($params),随后按"控制器 → 模块链"方向调用afterAction(),最终返回结果给应用。

值得补充的是,Web 控制器的beforeAction()还承担了CSRF 校验职责:当enableCsrfValidationtrue且请求的 CSRF Token 校验失败时,会抛出BadRequestHttpException(见 framework/web/Controller.php)。这解释了前文 controllerMap 示例中为何可以通过配置数组关闭某个控制器的 CSRF 校验。

最佳实践(Best Practices)

在良好的应用设计中,控制器往往非常轻薄(thin),每个 Action 只包含寥寥几行代码。如果你的控制器变得相当复杂,通常意味着你应该重构,把部分代码迁移到其他类中。

以下是几条具体的控制器实践建议。控制器:

  • 可以访问请求数据;
  • 可以用请求数据调用模型及其他服务组件的方法;
  • 可以使用视图来组装响应;
  • 不应处理请求数据——这应该由模型层完成;
  • 不应内嵌 HTML 或其他展示性代码——这更适合放在视图中。

遵循这些原则,可以让控制器保持职责单一、易于测试,也让模型与视图各司其职,整个应用的维护成本显著降低。

相关阅读

  • 应用结构总览:了解控制器在整个应用骨架中的位置
  • 模型 与 视图:控制器的两大协作对象
  • 路由与 URL 生成:路由如何被解析为 Action
  • 模块:在模块中使用控制器
  • 请求与响应 / 响应:Action 参数来源与返回值去向
  • 配置:controllerMap 与 defaultRoute 的配置语义
  • 核心源码:framework/base/Controller.php、framework/web/Controller.php、framework/base/Module.php、framework/base/Action.php
  • 后端
  • Web框架

【免费下载链接】yii2

Yii 2: The Fast, Secure and Professional PHP Framework

项目地址:https://gitcode.com/gh_mirrors/yi/yii2
点击查看免费下载

相关推荐

上一篇:VoiceprintRecognition-Pytorch深度解析:多模型声纹识别系统架构与生产级部署实践
下一篇:Respond.js性能监控:如何量化老旧浏览器中的优化效果

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询