- 后端
- Web框架
【免费下载链接】yii2
Yii 2: The Fast, Secure and Professional PHP Framework
本篇指南以 Yii 2 官方指南的控制器章节为骨架,系统讲解控制器在 MVC 架构 中的职责、路由(Route)的解析规则、Controller 与 Action 的创建规范、Action 参数绑定机制以及控制器完整生命周期。读完本文,你将能够正确命名与组织控制器和 Action、通过 controllerMap 接入第三方控制器、按需选用 Inline/Standalone Action,并深入理解请求从进入应用到产出响应的每一步内部机制。文章中的代码示例可直接复制运行,源码级证据均来自本仓库的 framework 目录。
控制器在 MVC 中的角色
控制器是 MVC 设计模式 的一部分。在 Yii 2 中,控制器是继承自yii\base\Controller的类对象(见 framework/base/Controller.php),其职责是处理请求并生成响应。
具体来说,当控制器从应用接过控制权后,会依次完成以下工作:
- 分析传入的请求数据;
- 将数据交给模型处理业务逻辑;
- 把模型的处理结果注入视图;
- 最终生成对外响应。
也就是说,控制器是模型与视图之间的"调度中枢":它自己不做业务计算,也不直接输出 HTML,而是协调两者完成一次完整的请求-响应闭环。
Actions:用户可寻址的最小执行单元
控制器由Action(动作)组成。Action 是终端用户可以直接寻址并要求执行的最小单元,一个控制器可以包含一个或多个 Action。
下面的示例定义了一个post控制器,包含view和create两个 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)中实现,其算法依次为:
- 路由为空时使用
defaultRoute; - 路由首段命中
controllerMap时,按其中的配置创建控制器; - 路由首段是已声明的模块 ID 时,将剩余部分交给该模块的
createController()递归解析; - 否则在
controllerNamespace下尝试abc\DefController或abc\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 只能包含以下字符:小写英文字母、数字、下划线、连字符(-)和正斜杠(/)。例如article、post-comment都是合法 ID,而article?、PostComment、admin\post均不合法。
控制器 ID 还可以包含子目录前缀。例如admin/article表示在controllerNamespace(控制器命名空间)下的admin子目录中名为article的控制器。子目录前缀允许的字符包括:大小写英文字母、数字、下划线和正斜杠,其中正斜杠用于分隔多级子目录(例如panels/admin)。在源码中,这一约束由Module::createControllerByID()中的isIncorrectClassNameOrPrefix()检查保证(见 framework/base/Module.php)。
控制器类命名(Controller Class Naming)
控制器类名按以下步骤从控制器 ID 推导:
- 将 ID 中每个以连字符分隔的单词首字母转为大写;注意若 ID 包含斜杠,该规则只作用于最后一个斜杠之后的部分;
- 删除连字符,并把正斜杠替换为反斜杠(命名空间分隔符);
- 追加后缀
Controller; - 前面加上
controllerNamespace。
假设controllerNamespace取默认值app\controllers(该默认值定义于 framework/base/Application.php),则:
| 控制器 ID | 推导出的类名 |
|---|---|
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这个例子展示了如何把控制器放进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 小节)。在源码中,controllerMap是yii\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方法,其返回值代表要发送给终端用户的响应数据。下面的代码定义了index和hello-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 多为动词,例如view、update等。
默认情况下,Action ID 只能包含:小写英文字母、数字、下划线和连字符(连字符用于连接单词)。例如view、update2、comment-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 推导:
- 将 ID 中每个单词的首字母转为大写;
- 删除连字符;
- 加上前缀
action。
例如,index→actionIndex,hello-world→actionHelloWorld。
注意:Action 方法名是区分大小写的。如果你定义了
ActionIndex这样的方法,它不会被当作 Action 方法,此时请求indexAction 将抛出异常。另外,Action 方法必须是public的,private或protected方法不会定义出 Inline Action。
这一点在源码中有严格对应:Controller::createAction()首先用正则/^(?:[a-z0-9_]+-)*[a-z0-9_]+$/校验 Action ID,然后通过ucwords与str_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\ViewAction和yii\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);若参数声明了int、float、bool等标量类型,则会通过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)
在处理请求时,应用会根据请求的路由创建控制器实例,然后控制器经历以下生命周期来完成请求:
- 初始化:控制器创建并完成配置后,调用
Controller::init()方法。基类中init()负责将request与response属性解析为对应的应用组件实例(见 framework/base/Controller.php)。 - 创建 Action 对象:控制器根据请求的 Action ID 创建动作对象:
- 若 Action ID 未指定,使用默认 Action ID(
defaultAction); - 若 Action ID 在
actions()返回的 action map 中命中,则创建 Standalone Action; - 若 Action ID 与某个 action 方法匹配,则创建 Inline Action;
- 否则抛出
yii\base\InvalidRouteException异常。
- 若 Action ID 未指定,使用默认 Action ID(
- beforeAction 阶段:控制器依次调用应用、模块(若控制器属于某模块)、控制器自身的
beforeAction()方法:- 若其中一次调用返回
false,则剩余的beforeAction()不再调用,Action 执行被取消; - 默认情况下,每次
beforeAction()调用都会触发一个beforeAction事件,你可以在事件上挂接处理器。在基类实现中,该事件通过ActionEvent携带动作信息,并以其isValid属性决定是否继续(见 framework/base/Controller.php)。
- 若其中一次调用返回
- 运行 Action:控制器执行 Action。此时 Action 参数会从请求数据中被解析并填充(即上文
bindActionParams()的绑定过程)。 - afterAction 阶段:控制器依次调用控制器自身、模块(若属于模块)、应用的
afterAction()方法:- 默认情况下,每次
afterAction()调用都会触发一个afterAction事件。基类实现中该事件的result属性会被作为最终返回值传递(见 framework/base/Controller.php)。
- 默认情况下,每次
- 生成响应:应用接收 Action 的执行结果,并将其赋值给响应组件。
上述编排逻辑的源码实现在Controller::runAction()中(见 framework/base/Controller.php):它先解析 Action,再沿"模块链 → 控制器"方向调用beforeAction(),成功则执行$action->runWithParams($params),随后按"控制器 → 模块链"方向调用afterAction(),最终返回结果给应用。
值得补充的是,Web 控制器的beforeAction()还承担了CSRF 校验职责:当enableCsrfValidation为true且请求的 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
相关推荐
Yii 2 控制器(Controller)完全指南:动作、路由、生命周期与最佳实践
Yii 2 控制器(Controller)完全指南:动作、路由、生命周期与最佳实践 控制器是 Yii 2 框架 MVC 架构的核心枢纽,它接收请求(reques
后端Web框架Yii 2 控制器(Controller)完全指南:路由、动作、生命周期与最佳实践
Yii 2 控制器(Controller)完全指南:路由、动作、生命周期与最佳实践 导读 控制器(Controller)是 Yii 2 中 MVC 架构的核心组
后端Web框架Yii 2 控制器(Controllers)完全指南:路由解析、动作机制与生命周期深度解析
Yii 2 控制器(Controllers)完全指南:路由解析、动作机制与生命周期深度解析 控制器(Controller)是 Yii 2 MVC 架构中的核心枢
后端Web框架
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考