Yii 2 RESTful 控制器开发指南:Controller 与 ActiveController 的源码级剖析
2026/9/23 8:33:40 网站建设 项目流程

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\Controlleryii\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。

ControllerActiveController共同提供以下能力(部分细节在后续章节展开):

  • HTTP 方法校验(method validation);
  • 内容协商与数据格式化(content negotiation and data formatting);
  • 用户认证(authentication);
  • 限流(rate limiting)。

ActiveController额外提供:

  • 一组常用动作:indexviewcreateupdatedeleteoptions
  • 针对所请求的动作与资源进行的用户授权(通过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-CountX-Pagination-Page-CountX-Pagination-Current-PageX-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()]]方法中,按如下顺序执行:

  1. contentNegotiatoryii\filters\ContentNegotiator):内容协商,详见响应格式化;
  2. verbFilteryii\filters\VerbFilter):HTTP 方法校验;
  3. authenticatoryii\filters\auth\AuthMethod):用户认证,详见认证;
  4. rateLimiteryii\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/jsonapplication/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 方法
indexyii\rest\IndexAction分页列出资源集合GETHEAD
viewyii\rest\ViewAction返回指定资源的详情GETHEAD
createyii\rest\CreateAction创建新资源POST
updateyii\rest\UpdateAction更新已有资源PUTPATCH
deleteyii\rest\DeleteAction删除指定资源DELETE
optionsyii\rest\OptionsAction返回支持的 HTTP 方法OPTIONS

方法与动作的默认映射来自ActiveController::verbs()(framework/rest/ActiveController.php)。从ActiveController::actions()的声明可以看到,每个 CRUD 动作都被注入了modelClass与指向控制器checkAccess()方法的checkAccess回调;createupdate还会分别携带createScenarioupdateScenario场景(默认均为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与搜索模型进行结构化过滤;
    • paginationsort(自 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):默认在响应头中输出AllowAccess-Control-Allow-Methods;集合 URL(无id)使用collectionOptionsGET, POST, HEAD, OPTIONS),资源 URL(有id)使用resourceOptionsGET, 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)调用,此时没有具体模型$modelnull);
  • ViewActionUpdateActionDeleteActionfindModel($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),仅供参考

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

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

立即咨询