Yii2 REST 响应格式化指南:Content Negotiation、Serializer 与 JSON/XML 输出控制
2026/9/24 15:50:01 网站建设 项目流程
  • 后端
  • Web框架

【免费下载链接】yii2

Yii 2: The Fast, Secure and Professional PHP Framework

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

RESTful API 请求处理中,响应格式化决定客户端最终收到的数据形态:资源对象如何变成数组、数组又如何变成 JSON 或 XML 字符串。本篇以 Yii2 框架的docs/guide/rest-response-formatting.md为骨架,结合 framework/rest 下的ControllerSerializer与 framework/web 的ResponseJsonResponseFormatter源码,完整讲解内容协商(Content Negotiation)、数据序列化(Data Serializing)以及 JSON 输出控制三个环节,读者可据此掌握响应格式的协商机制、分页信封配置与 JSON 编码调优,并能在自己的 API 控制器中落地实践。

REST 响应格式化的三个阶段

当 Yii2 应用处理一个 RESTful API 请求时,与响应格式化相关的步骤通常如下:

  1. 确定影响响应格式的各种因素,如媒体类型(media type)、语言、版本等,这一过程即内容协商(Content Negotiation);
  2. 将资源对象转换为数组,该步骤由 yii\rest\Serializer 完成,具体转换规则在 Resources(资源) 一节中说明;
  3. 将数组按内容协商确定的格式转换为字符串,由注册在response应用组件 的 yii\web\Response::formatters 属性中的 yii\web\ResponseFormatterInterface 响应格式化器完成。

从源码看,yii\rest\ControllerafterAction()会调用serializeData()返回结果,而serializeData()使用Yii::createObject($this->serializer)->serialize($data)创建并调用序列化器(见 framework/rest/Controller.php),随后Response::prepare()根据formatters中对应格式的格式化器将数组数据渲染成响应内容(见 framework/web/Response.php)。三个阶段由此在控制器与响应对象之间串联成完整的格式化流水线。

Content Negotiation 内容协商

Yii2 通过 yii\filters\ContentNegotiator 过滤器支持内容协商。RESTful API 基础控制器类 yii\rest\Controller 内置了名为contentNegotiator的过滤器,提供响应格式协商语言协商两种能力。

协商过程与典型效果

该过滤器在 RESTful API 控制器动作执行前检查请求的Accept请求头,并将 yii\web\Response::format 设置为对应格式。例如,若请求包含如下请求头:

Accept: application/json; q=1.0, */*; q=0.1

将得到 JSON 格式的响应:

$ curl -i -H "Accept: application/json; q=1.0, */*; q=0.1" "http://localhost/users" HTTP/1.1 200 OK Date: Sun, 02 Mar 2014 05:31:43 GMT Server: Apache/2.2.26 (Unix) DAV/2 PHP/5.4.20 mod_ssl/2.2.26 OpenSSL/0.9.8y X-Powered-By: PHP/5.4.20 X-Pagination-Total-Count: 1000 X-Pagination-Page-Count: 50 X-Pagination-Current-Page: 1 X-Pagination-Per-Page: 20 Link: <http://localhost/users?page=1>; rel=self, <http://localhost/users?page=2>; rel=next, <http://localhost/users?page=50>; rel=last Transfer-Encoding: chunked Content-Type: application/json; charset=UTF-8 [ { "id": 1, ... }, { "id": 2, ... }, ... ]

整个流程如下:动作执行前ContentNegotiator过滤器检查Accept请求头并把响应格式设置为'json';动作执行后返回资源对象或集合,yii\rest\Serializer将结果转换为数组;最后由 yii\web\JsonResponseFormatter 把数组序列化为 JSON 字符串并写入响应体。

协商的底层实现要点

从 framework/filters/ContentNegotiator.php 的源码可以看到协商的完整逻辑:

  • negotiate()会先处理formats,若支持多于一种格式,自动向响应添加Vary: Accept响应头,以利于 HTTP 缓存按Accept头区分缓存;
  • 若存在formatParam(默认_format)GET 参数,则优先按该参数直接指定格式:参数值合法则直接设置Response::format,否则抛出 yii\web\NotAcceptableHttpException(状态码 406);参数为数组时抛出 yii\web\BadRequestHttpException(400);
  • _format参数时,遍历$request->getAcceptableContentTypes(),按 q 值优先级在formats中匹配 MIME 类型;匹配成功则同时设置Response::formatacceptMimeTypeacceptParams
  • 若所有可接受类型都不匹配,会回退到formats中的第一个格式;仅当请求中没有*/*通配时才抛出 406 异常。

语言协商的逻辑类似(languageParam默认_langlanguages支持带键映射与无键前缀回退,例如en可匹配en-USen-GB),协商结果写入Yii::$app->language

扩展新格式

默认情况下 RESTful API 同时支持 JSON 与 XML 两种格式(application/jsonjsonapplication/xmlxml,见 framework/rest/Controller.php)。若需支持新格式,可在 API 控制器类中配置contentNegotiator过滤器的 formats 属性:

use yii\web\Response; public function behaviors() { $behaviors = parent::behaviors(); $behaviors['contentNegotiator']['formats']['text/html'] = Response::FORMAT_HTML; return $behaviors; }

formats属性的键是支持的 MIME 类型,值是相应的响应格式名称,且该名称必须存在于 yii\web\Response::formatters 中。Response 默认注册的格式包括htmlHtmlResponseFormatter)、xmlXmlResponseFormatter)、jsonJsonResponseFormatter)与jsonpJsonResponseFormatteruseJsonptrue)。

另外,ContentNegotiator既可作动作过滤器使用,也可作为应用级的bootstrap组件使用(它实现了BootstrapInterface),后者可对整个应用生效,配置方式参见 framework/filters/ContentNegotiator.php 中的注释示例。

Data Serializing 数据序列化

yii\rest\Serializer 是负责把资源对象或集合转换为数组的核心组件。它识别实现 yii\base\Arrayable 的对象(主要是资源对象)以及实现 yii\data\DataProviderInterface 的对象(资源集合)。

serialize() 的分派逻辑

从源码 framework/rest/Serializer.php 可以看到serialize()按以下顺序分派:

  1. 若数据是带校验错误的Model$data->hasErrors()true),调用serializeModelErrors():将响应状态码设置为422("Data Validation Failed."),并输出[{'field' => ..., 'message' => ...}, ...]结构的错误数组(见 framework/rest/Serializer.php);
  2. 若数据实现Arrayable,调用serializeModel()并委托$model->toArray($fields, $expand)
  3. 若数据实现\JsonSerializable,调用jsonSerialize()
  4. 若数据实现DataProviderInterface,调用serializeDataProvider()
  5. 若数据是数组,则递归对每个元素调用serialize()
  6. 其他类型原样返回。

序列化器的fieldsParam(默认fields)与expandParam(默认expand)支持客户端通过查询参数控制返回字段,getRequestedFields()会把逗号分隔的参数解析为字段列表(见 framework/rest/Serializer.php)。

配置序列化器与 collectionEnvelope

可通过设置 yii\rest\Controller::serializer 属性为配置数组来定制序列化器。例如,若希望把分页信息直接放进响应体以简化客户端开发,可配置 yii\rest\Serializer::collectionEnvelope 属性:

use yii\rest\ActiveController; class UserController extends ActiveController { public $modelClass = 'app\models\User'; public $serializer = [ 'class' => 'yii\rest\Serializer', 'collectionEnvelope' => 'items', ]; }

之后请求http://localhost/users将得到如下响应:

HTTP/1.1 200 OK Date: Sun, 02 Mar 2014 05:31:43 GMT Server: Apache/2.2.26 (Unix) DAV/2 PHP/5.4.20 mod_ssl/2.2.26 OpenSSL/0.9.8y X-Powered-By: PHP/5.4.20 X-Pagination-Total-Count: 1000 X-Pagination-Page-Count: 50 X-Pagination-Current-Page: 1 X-Pagination-Per-Page: 20 Link: <http://localhost/users?page=1>; rel=self, <http://localhost/users?page=2>; rel=next, <http://localhost/users?page=50>; rel=last Transfer-Encoding: chunked Content-Type: application/json; charset=UTF-8 { "items": [ { "id": 1, ... }, { "id": 2, ... }, ... ], "_links": { "self": { "href": "http://localhost/users?page=1" }, "next": { "href": "http://localhost/users?page=2" }, "last": { "href": "http://localhost/users?page=50" } }, "_meta": { "totalCount": 1000, "pageCount": 50, "currentPage": 1, "perPage": 20 } }

可见启用信封后,响应体结构变为{ items: [...], _links: {...}, _meta: {...} },其中_linksPagination::getLinks(true)生成,_meta由分页对象的总数、页数、当前页与每页条数组成(见 framework/rest/Serializer.php)。同时原有分页 HTTP 头(X-Pagination-*Link)仍然保留——serializeDataProvider()在返回数据前会调用addPaginationHeaders()写入这些头(见 framework/rest/Serializer.php),默认头名称由totalCountHeaderpageCountHeadercurrentPageHeaderperPageHeader四个属性控制。

Serializer 还提供其他实用属性:

  • linksEnvelope(默认_links)与metaEnvelope(默认_meta):仅在collectionEnvelope设置时生效,可自定义信封键名(自 2.0.4 起);
  • preserveKeys(默认false,自 2.0.10 起):设为true时保留集合数组的键,可将集合序列化为以键索引的 JSON 对象而非数组;
  • 对于HEAD请求,serializeModel()serializeDataProvider()直接返回null,从而不产生响应体(见 framework/rest/Serializer.php 与第 262-270 行)。

对应行为在 tests/framework/rest/SerializerTest.php 中有完整的单元测试覆盖,可作为理解各种属性组合效果的可执行参考。

Controlling JSON Output 控制 JSON 输出

JSON 响应由 yii\web\JsonResponseFormatter 生成,内部使用 yii\helpers\Json(JSON 助手)。该格式化器可在response应用组件的 formatters 属性中配置(应用配置参见 concept-configurations):

'response' => [ // ... 'formatters' => [ \yii\web\Response::FORMAT_JSON => [ 'class' => 'yii\web\JsonResponseFormatter', 'prettyPrint' => YII_DEBUG, // use "pretty" output in debug mode 'encodeOptions' => JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE, // ... ], ], ],

常用格式化选项

  • prettyPrint:默认false,设为true时会在编码选项上追加JSON_PRETTY_PRINT,输出易读的格式化 JSON,适合开发调试环境(如示例中的YII_DEBUG);
  • encodeOptions:默认值320,即JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE(见 framework/web/JsonResponseFormatter.php),可按 PHP 的json_encode()选项位掩码自由组合,例如增加JSON_NUMERIC_CHECK强制数字字符串转为数字;
  • contentType:自 2.0.14 起支持自定义Content-Type响应头,默认按useJsonp取值,分别为application/json; charset=UTF-8application/javascript; charset=UTF-8
  • useJsonp:开启 JSONP 模式时,要求响应数据是包含datacallback两个成员的数组,输出形如callback(data);;若数据不满足要求会记录 warning(见 framework/web/JsonResponseFormatter.php);
  • keepObjectType:自 2.0.44 起,设为true可避免零起始索引的数组被编码为 JSON 数组,而是按对象输出(与json_encode()行为一致)。

从 framework/web/JsonResponseFormatter.php 的实现可以看出:格式化时若prettyPrint为真则在encodeOptions上按位或JSON_PRETTY_PRINT,随后调用Json::encode($response->data, $options)写入$response->contentkeepObjectType在编码前后临时调整Json::$keepObjectType并恢复,以避免影响后续请求。

关于数值类型的一个关键提醒

使用 DAO 数据库层返回的数据一律以字符串形式表示,这在 JSON 中并不总是期望的结果——尤其是数值字段本应以数字类型呈现。而使用 ActiveRecord 层获取数据库数据时,数值列的值会在 yii\db\ActiveRecord::populateRecord() 中按表结构的列类型进行 PHP 类型转换(phpTypecast),因此返回的数值字段会成为整数/浮点数,从而在 JSON 中正确输出为数字而非字符串。这是选择 DAO 还是 ActiveRecord 输出 API 数据时需要注意的差异点。

小结

Yii2 的 REST 响应格式化由「内容协商 → 数据序列化 → 格式化器输出」三段流水线构成:ContentNegotiator依据Accept头与_format参数确定Response::formatSerializer将资源对象/数据提供器转换为数组并注入分页信息(头或信封),JsonResponseFormatter/XmlResponseFormatter等格式化器最终把数组渲染为响应体字符串。开发者可通过contentNegotiatorformats扩展媒体类型,通过控制器serializer属性定制collectionEnvelope等信封与键保留行为,并在response组件中精细控制prettyPrintencodeOptions等 JSON 编码细节,从而构建出格式协商灵活、数据结构可控、编码风格统一的高质量 RESTful API。

  • 后端
  • Web框架

【免费下载链接】yii2

Yii 2: The Fast, Secure and Professional PHP Framework

项目地址:https://gitcode.com/gh_mirrors/yi/yii2
点击查看免费下载
上一篇:如何用NLTK分词?word_tokenize、Punkt、正则8种分词器实战对比与选型教程
下一篇:如何把一张发灰的 RAW 救回出片:darktable 暗房 6 个关键模块实战指南

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

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

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

立即咨询