十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Yii 2 RESTful 控制器开发指南:Controller 与 ActiveController 的源码级剖析

Yii 2 RESTful 控制器开发指南:Controller 与 ActiveController 的源码级剖析 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\Serializerframework/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-Pageframework/rest/Serializer.phpcollectionEnvelope为资源集合指定外层信封如items配合_links与_meta返回分页链接和元信息framework/rest/Serializer.phppreserveKeys是否在序列化集合时保留数组键默认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()]]方法中按如下顺序执行contentNegotiatoryii\filters\ContentNegotiator内容协商详见响应格式化verbFilteryii\filters\VerbFilterHTTP 方法校验authenticatoryii\filters\auth\AuthMethod用户认证详见认证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/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 方法indexyii\rest\IndexAction分页列出资源集合GET、HEADviewyii\rest\ViewAction返回指定资源的详情GET、HEADcreateyii\rest\CreateAction创建新资源POSTupdateyii\rest\UpdateAction更新已有资源PUT、PATCHdeleteyii\rest\DeleteAction删除指定资源DELETEoptionsyii\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 各默认动作的底层行为为了让你理解默认动作免费获得了什么这里结合源码说明每个动作类的关键行为IndexActionframework/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 ...。ViewActionframework/rest/ViewAction.php调用基类Action::findModel($id)查找模型找到后执行checkAccess($this-id, $model)并返回模型。CreateActionframework/rest/CreateAction.php以指定场景创建新模型从请求体加载数据$model-load($request-getBodyParams(), )保存成功后将响应状态码设为201并返回Location头指向view动作若保存失败且没有校验错误则抛出ServerErrorHttpException有校验错误时直接返回模型由序列化器输出错误信息。UpdateActionframework/rest/UpdateAction.php先findModel($id)再checkAccess($this-id, $model)随后加载请求体并保存保存失败且无校验错误时抛出ServerErrorHttpException。DeleteActionframework/rest/DeleteAction.phpfindModel($id)后执行访问检查删除成功则把响应状态码设为204删除失败抛出ServerErrorHttpException。OptionsActionframework/rest/OptionsAction.php默认在响应头中输出Allow与Access-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参数须用逗号分隔多个主键值找不到模型时抛出NotFoundHttpException404。你还可以通过$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为nullViewAction、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 RecordActiveController所服务的资源模型基础过滤器行为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),仅供参考
返回列表