Yii 2 RESTful 控制器开发指南:Controller 与 ActiveController 的源码级剖析
【免费下载链接】yii2Yii 2: The Fast, Secure and Professional PHP Framework项目地址: https://gitcode.com/gh_mirrors/yi/yii2
导读
本文基于 Yii 2 官方指南《rest-controllers》(英文版、西班牙语版),系统讲解如何通过yii\rest\Controller与yii\rest\ActiveController两个基类快速构建 RESTful API 控制器。你将掌握 REST 控制器的命名约定与动作编写方式、内建过滤器(内容协商、HTTP 方法校验、认证、限流)的执行顺序与定制方法、ActiveController六种默认动作的底层实现,以及通过actions()与checkAccess()实现动作定制和访问控制的具体写法。全文结合 framework/rest 目录下的真实源码与 tests/framework/rest 中的测试用例进行印证,既可直接落地实践,也能帮助你理解框架内部的请求处理链路。
1. 为什么需要 REST 控制器基类
在创建资源类(如 Active Record 模型)并配置好数据格式之后,下一步就是把资源通过 RESTful API 暴露给终端用户,而这一步的核心就是编写控制器动作。Yii 2 提供了两个控制器基类来简化这一工作:
[[yii\rest\Controller]]:通用 REST 控制器基类;[[yii\rest\ActiveController]]:在Controller基础上,为以 Active Record 形式存在的资源提供一套开箱即用的默认动作集。
两者的关系正如 framework/rest/ActiveController.php 所示:class ActiveController extends Controller。如果你正在使用 Active Record 并且对内置动作感到满意,直接继承ActiveController就能用极少的代码搭建出功能完整的 RESTful API。
Controller与ActiveController共同提供以下能力(部分细节在后续章节展开):
- HTTP 方法校验(method validation);
- 内容协商与数据格式化(content negotiation and data formatting);
- 用户认证(authentication);
- 限流(rate limiting)。
ActiveController额外提供:
- 一组常用动作:
index、view、create、update、delete、options; - 针对所请求的动作与资源进行的用户授权(通过
checkAccess()实现)。
从源码看,这些能力的执行顺序被完整地记录在 framework/rest/Controller.php 的类注释中:解析响应格式 → 校验请求方法 → 认证用户 → 限流 → 格式化响应数据,这一链路正是由下文的过滤器与序列化器共同实现的。
2. 创建控制器类与动作
2.1 命名约定
创建 REST 控制器类时,惯例是使用资源类型的单数形式作为类名。例如,要为user资源提供服务,控制器可以命名为UserController。
2.2 动作与 Web 动作的区别
编写 REST 动作与编写普通 Web 应用动作非常相似,唯一区别在于:REST 动作不再调用render()渲染视图,而是直接把数据作为返回值。数据到请求格式的转换由[[yii\rest\Controller::serializer|serializer]]与[[yii\web\Response|response object]]协作完成。例如:
public function actionView($id) { return User::findOne($id); }这里返回的是一个UserActive Record 实例,序列化器会将其转换为数组,再由响应对象按照协商出的格式(JSON/XML 等)输出。
序列化发生在哪里?源码中,Controller::afterAction()在动作执行完毕后调用serializeData($result),后者通过Yii::createObject($this->serializer)->serialize($data)创建序列化器并处理返回数据(见 framework/rest/Controller.php)。默认的serializer配置为'yii\rest\Serializer'(framework/rest/Controller.php)。
Serializer是理解 REST 输出行为的关键。以 framework/rest/Serializer.php 为例,它支持:
fieldsParam(默认fields)与expandParam(默认expand):控制资源对象返回哪些字段、额外展开哪些关联字段;- 分页相关 HTTP 头:
X-Pagination-Total-Count、X-Pagination-Page-Count、X-Pagination-Current-Page、X-Pagination-Per-Page(framework/rest/Serializer.php); collectionEnvelope:为资源集合指定外层信封(如items),配合_links与_meta返回分页链接和元信息(framework/rest/Serializer.php);preserveKeys:是否在序列化集合时保留数组键(默认false,自 2.0.10 起)。
Serializer::serialize()会识别不同类型的数据:Model带错误时输出错误信息、Arrayable对象输出字段数组、JsonSerializable对象输出其 JSON 表示、DataProviderInterface输出带分页信息的集合(framework/rest/Serializer.php)。
2.3 关于 CSRF 的说明
REST API 通常不使用基于会话的 CSRF 防护。Controller中显式设置了public $enableCsrfValidation = false;(framework/rest/Controller.php),这是 REST 控制器与普通 Web 控制器的又一处重要差异。
3. 过滤器:REST 特性的实现基石
Controller提供的大部分 REST 特性都是通过过滤器(filters)实现的。过滤器以行为(behavior)的形式声明在[[yii\rest\Controller::behaviors()|behaviors()]]方法中,按如下顺序执行:
contentNegotiator(yii\filters\ContentNegotiator):内容协商,详见响应格式化;verbFilter(yii\filters\VerbFilter):HTTP 方法校验;authenticator(yii\filters\auth\AuthMethod):用户认证,详见认证;rateLimiter(yii\filters\RateLimiter):限流,详见限流。
这些过滤器的默认声明直接体现在 framework/rest/Controller.php 中:
public function behaviors() { return [ 'contentNegotiator' => [ 'class' => ContentNegotiator::className(), 'formats' => [ 'application/json' => Response::FORMAT_JSON, 'application/xml' => Response::FORMAT_XML, ], ], 'verbFilter' => [ 'class' => VerbFilter::className(), 'actions' => $this->verbs(), ], 'authenticator' => [ 'class' => CompositeAuth::className(), ], 'rateLimiter' => [ 'class' => RateLimiter::className(), ], ]; }注意两点细节:
contentNegotiator默认已协商application/json与application/xml两种格式;verbFilter所校验的方法由verbs()方法返回,基类默认返回空数组,ActiveController则提供了针对各动作的默认映射(见第 4.3 节)。
定制过滤器:你可以重写behaviors()方法,来调整单个过滤器配置、禁用某些过滤器,或追加自己的过滤器。例如,只想使用 HTTP Basic 认证时:
use yii\filters\auth\HttpBasicAuth; public function behaviors() { $behaviors = parent::behaviors(); $behaviors['authenticator'] = [ 'class' => HttpBasicAuth::class, ]; return $behaviors; }注意这里authenticator的默认类是CompositeAuth(支持同时挂载多种认证方式),将其替换为HttpBasicAuth即只启用 Basic 认证。
3.1 过滤器执行顺序的意义
contentNegotiator排在首位,保证了后续认证、限流等环节在输出前就已确定响应格式;verbFilter在认证之前校验 HTTP 方法,避免对非法的请求方法执行多余的业务逻辑。这一顺序是 REST 控制器请求处理循环的一部分,完整链路见 framework/rest/Controller.php 的注释。
4. 继承 ActiveController
如果你的控制器继承自[[yii\rest\ActiveController]],则必须设置其[[yii\rest\ActiveController::modelClass|modelClass]]属性,指定该控制器要服务的资源类,且该类必须继承自[[yii\db\ActiveRecord]]:
class UserController extends ActiveController { public $modelClass = 'app\models\User'; }若未设置modelClass,控制器初始化时会在init()中抛出InvalidConfigException,提示The "modelClass" property must be set.(见 framework/rest/ActiveController.php)。
4.1 默认动作一览
ActiveController默认提供以下六个动作(声明于 framework/rest/ActiveController.php):
| 动作 | 动作类 | 说明 | HTTP 方法 |
|---|---|---|---|
index | yii\rest\IndexAction | 分页列出资源集合 | GET、HEAD |
view | yii\rest\ViewAction | 返回指定资源的详情 | GET、HEAD |
create | yii\rest\CreateAction | 创建新资源 | POST |
update | yii\rest\UpdateAction | 更新已有资源 | PUT、PATCH |
delete | yii\rest\DeleteAction | 删除指定资源 | DELETE |
options | yii\rest\OptionsAction | 返回支持的 HTTP 方法 | OPTIONS |
方法与动作的默认映射来自ActiveController::verbs()(framework/rest/ActiveController.php)。从ActiveController::actions()的声明可以看到,每个 CRUD 动作都被注入了modelClass与指向控制器checkAccess()方法的checkAccess回调;create与update还会分别携带createScenario与updateScenario场景(默认均为Model::SCENARIO_DEFAULT,见 framework/rest/ActiveController.php)。
4.2 各默认动作的底层行为
为了让你理解默认动作"免费获得"了什么,这里结合源码说明每个动作类的关键行为:
IndexAction(framework/rest/IndexAction.php):先执行checkAccess($this->id),然后准备数据提供器。默认逻辑会以modelClass::find()为查询,配合请求参数构造ActiveDataProvider,并支持:prepareDataProvider回调:完全接管数据提供器的构建(对应文档中的定制示例);prepareSearchQuery回调(自 2.0.42 起):在默认查询基础上追加过滤条件,签名形如function ($query, $requestParams);dataFilter(自 2.0.13 起):配合yii\data\ActiveDataFilter与搜索模型进行结构化过滤;pagination与sort(自 2.0.45 起):可传入数组、Pagination/Sort对象或false(禁用分页/排序)。
在 tests/framework/rest/IndexActionTest.php 的测试中,通过
prepareSearchQuery回调捕获了最终 SQL,验证了"默认查询 + 搜索回调"的执行链路(SELECT * FROM ...)。ViewAction(framework/rest/ViewAction.php):调用基类Action::findModel($id)查找模型,找到后执行checkAccess($this->id, $model)并返回模型。CreateAction(framework/rest/CreateAction.php):以指定场景创建新模型,从请求体加载数据($model->load($request->getBodyParams(), '')),保存成功后将响应状态码设为201并返回Location头(指向view动作);若保存失败且没有校验错误,则抛出ServerErrorHttpException;有校验错误时直接返回模型,由序列化器输出错误信息。UpdateAction(framework/rest/UpdateAction.php):先findModel($id),再checkAccess($this->id, $model),随后加载请求体并保存;保存失败且无校验错误时抛出ServerErrorHttpException。DeleteAction(framework/rest/DeleteAction.php):findModel($id)后执行访问检查,删除成功则把响应状态码设为204;删除失败抛出ServerErrorHttpException。OptionsAction(framework/rest/OptionsAction.php):默认在响应头中输出Allow与Access-Control-Allow-Methods;集合 URL(无id)使用collectionOptions(GET, POST, HEAD, OPTIONS),资源 URL(有id)使用resourceOptions(GET, PUT, PATCH, DELETE, HEAD, OPTIONS);非OPTIONS请求访问该动作时返回405。Action::findModel()(framework/rest/Action.php):默认按主键查找模型。对于复合主键,id参数须用逗号分隔多个主键值;找不到模型时抛出NotFoundHttpException(404)。你还可以通过$findModel回调自定义查找逻辑,签名形如function ($id, $action)。
4.3 定制与禁用动作
所有默认动作都通过actions()方法声明,因此你可以重写actions()来配置、禁用或替换它们:
public function actions() { $actions = parent::actions(); // 禁用 "delete" 与 "create" 动作 unset($actions['delete'], $actions['create']); // 通过 "prepareDataProvider()" 方法定制 index 的数据提供器构建 $actions['index']['prepareDataProvider'] = [$this, 'prepareDataProvider']; return $actions; } public function prepareDataProvider() { // 为 "index" 动作准备并返回一个数据提供器 }若需了解每个动作类支持的全部配置项,可查看对应动作类的类引用(即上文第 4.2 节所列的源码文件)。
4.4 新增自定义动作的注意事项
ActiveController的类注释(framework/rest/ActiveController.php)给出了一条重要提醒:新增动作时,既可以重写actions()追加新的动作类,也可以直接编写新的动作方法;但务必同时重写verbs(),为新动作正确声明允许的 HTTP 方法(例如新动作只接受GET就声明['GET', 'HEAD']),否则VerbFilter会拒绝对应方法的请求。
5. 访问控制:checkAccess()
在通过 RESTful API 暴露资源时,经常需要校验当前用户是否有权限访问和操作所请求的资源。借助ActiveController,重写[[yii\rest\ActiveController::checkAccess()|checkAccess()]]即可实现:
/** * Checks the privilege of the current user. * * This method should be overridden to check whether the current user has the privilege * to run the specified action against the specified data model. * If the user does not have access, a [[ForbiddenHttpException]] should be thrown. * * @param string $action the ID of the action to be executed * @param \yii\base\Model $model the model to be accessed. If `null`, it means no specific model is being accessed. * @param array $params additional parameters * @throws ForbiddenHttpException if the user does not have access */ public function checkAccess($action, $model = null, $params = []) { // 检查用户是否能访问 $action 与 $model // 如果应拒绝访问,则抛出 ForbiddenHttpException if ($action === 'update' || $action === 'delete') { if ($model->author_id !== \Yii::$app->user->id) throw new \yii\web\ForbiddenHttpException(sprintf('You can only %s articles that you\'ve created.', $action)); } }关于checkAccess()的调用时机,从源码可以确认:
IndexAction::run()以call_user_func($this->checkAccess, $this->id)调用,此时没有具体模型($model为null);ViewAction、UpdateAction、DeleteAction在findModel($id)之后以call_user_func($this->checkAccess, $this->id, $model)调用,可以拿到具体模型实例;CreateAction在创建模型、加载数据之前以$this->id调用(无模型)。
重要:checkAccess()只会被ActiveController的默认动作自动调用。如果你创建了新的动作,并希望同样执行访问检查,必须在新动作中显式调用该方法(这也是Action::$checkAccess属性的设计用途,见 framework/rest/Action.php)。
Tip:你可以借助 基于角色的访问控制(RBAC)组件 来实现
checkAccess(),例如在其中调用 RBAC 的权限检查逻辑,把粗粒度的动作级校验与细粒度的资源级校验统一起来。
6. 关联阅读
REST 控制器的能力与以下主题紧密衔接,建议按需阅读:
- 响应格式化与内容协商:
contentNegotiator过滤器与Serializer的输出细节; - 认证:
authenticator过滤器支持的多种认证方式; - 限流:
rateLimiter过滤器与RateLimiter接口的实现要求; - Active Record:
ActiveController所服务的资源模型基础; - 过滤器:行为(behavior)与过滤器机制的通用说明;
- RBAC 授权:在
checkAccess()中接入角色权限检查。
源码与测试参考路径:
- 控制器基类:framework/rest/Controller.php、framework/rest/ActiveController.php;
- 动作类:framework/rest/IndexAction.php、framework/rest/ViewAction.php、framework/rest/CreateAction.php、framework/rest/UpdateAction.php、framework/rest/DeleteAction.php、framework/rest/OptionsAction.php、framework/rest/Action.php;
- 序列化器:framework/rest/Serializer.php;
- 测试用例:tests/framework/rest/IndexActionTest.php。
【免费下载链接】yii2Yii 2: The Fast, Secure and Professional PHP Framework项目地址: https://gitcode.com/gh_mirrors/yi/yii2
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考