- 后端
- Web框架
【免费下载链接】yii2
Yii 2: The Fast, Secure and Professional PHP Framework
RESTful API 请求处理中,响应格式化决定客户端最终收到的数据形态:资源对象如何变成数组、数组又如何变成 JSON 或 XML 字符串。本篇以 Yii2 框架的docs/guide/rest-response-formatting.md为骨架,结合 framework/rest 下的Controller、Serializer与 framework/web 的Response、JsonResponseFormatter源码,完整讲解内容协商(Content Negotiation)、数据序列化(Data Serializing)以及 JSON 输出控制三个环节,读者可据此掌握响应格式的协商机制、分页信封配置与 JSON 编码调优,并能在自己的 API 控制器中落地实践。
REST 响应格式化的三个阶段
当 Yii2 应用处理一个 RESTful API 请求时,与响应格式化相关的步骤通常如下:
- 确定影响响应格式的各种因素,如媒体类型(media type)、语言、版本等,这一过程即内容协商(Content Negotiation);
- 将资源对象转换为数组,该步骤由 yii\rest\Serializer 完成,具体转换规则在 Resources(资源) 一节中说明;
- 将数组按内容协商确定的格式转换为字符串,由注册在
response应用组件 的 yii\web\Response::formatters 属性中的 yii\web\ResponseFormatterInterface 响应格式化器完成。
从源码看,yii\rest\Controller的afterAction()会调用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::format、acceptMimeType与acceptParams; - 若所有可接受类型都不匹配,会回退到
formats中的第一个格式;仅当请求中没有*/*通配时才抛出 406 异常。
语言协商的逻辑类似(languageParam默认_lang,languages支持带键映射与无键前缀回退,例如en可匹配en-US、en-GB),协商结果写入Yii::$app->language。
扩展新格式
默认情况下 RESTful API 同时支持 JSON 与 XML 两种格式(application/json→json、application/xml→xml,见 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 默认注册的格式包括html(HtmlResponseFormatter)、xml(XmlResponseFormatter)、json(JsonResponseFormatter)与jsonp(JsonResponseFormatter且useJsonp为true)。
另外,ContentNegotiator既可作动作过滤器使用,也可作为应用级的bootstrap组件使用(它实现了BootstrapInterface),后者可对整个应用生效,配置方式参见 framework/filters/ContentNegotiator.php 中的注释示例。
Data Serializing 数据序列化
yii\rest\Serializer 是负责把资源对象或集合转换为数组的核心组件。它识别实现 yii\base\Arrayable 的对象(主要是资源对象)以及实现 yii\data\DataProviderInterface 的对象(资源集合)。
serialize() 的分派逻辑
从源码 framework/rest/Serializer.php 可以看到serialize()按以下顺序分派:
- 若数据是带校验错误的
Model($data->hasErrors()为true),调用serializeModelErrors():将响应状态码设置为422("Data Validation Failed."),并输出[{'field' => ..., 'message' => ...}, ...]结构的错误数组(见 framework/rest/Serializer.php); - 若数据实现
Arrayable,调用serializeModel()并委托$model->toArray($fields, $expand); - 若数据实现
\JsonSerializable,调用jsonSerialize(); - 若数据实现
DataProviderInterface,调用serializeDataProvider(); - 若数据是数组,则递归对每个元素调用
serialize(); - 其他类型原样返回。
序列化器的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: {...} },其中_links由Pagination::getLinks(true)生成,_meta由分页对象的总数、页数、当前页与每页条数组成(见 framework/rest/Serializer.php)。同时原有分页 HTTP 头(X-Pagination-*与Link)仍然保留——serializeDataProvider()在返回数据前会调用addPaginationHeaders()写入这些头(见 framework/rest/Serializer.php),默认头名称由totalCountHeader、pageCountHeader、currentPageHeader、perPageHeader四个属性控制。
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-8或application/javascript; charset=UTF-8;useJsonp:开启 JSONP 模式时,要求响应数据是包含data与callback两个成员的数组,输出形如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->content;keepObjectType在编码前后临时调整Json::$keepObjectType并恢复,以避免影响后续请求。
关于数值类型的一个关键提醒
使用 DAO 数据库层返回的数据一律以字符串形式表示,这在 JSON 中并不总是期望的结果——尤其是数值字段本应以数字类型呈现。而使用 ActiveRecord 层获取数据库数据时,数值列的值会在 yii\db\ActiveRecord::populateRecord() 中按表结构的列类型进行 PHP 类型转换(phpTypecast),因此返回的数值字段会成为整数/浮点数,从而在 JSON 中正确输出为数字而非字符串。这是选择 DAO 还是 ActiveRecord 输出 API 数据时需要注意的差异点。
小结
Yii2 的 REST 响应格式化由「内容协商 → 数据序列化 → 格式化器输出」三段流水线构成:ContentNegotiator依据Accept头与_format参数确定Response::format,Serializer将资源对象/数据提供器转换为数组并注入分页信息(头或信封),JsonResponseFormatter/XmlResponseFormatter等格式化器最终把数组渲染为响应体字符串。开发者可通过contentNegotiator的formats扩展媒体类型,通过控制器serializer属性定制collectionEnvelope等信封与键保留行为,并在response组件中精细控制prettyPrint、encodeOptions等 JSON 编码细节,从而构建出格式协商灵活、数据结构可控、编码风格统一的高质量 RESTful API。
- 后端
- Web框架
【免费下载链接】yii2
Yii 2: The Fast, Secure and Professional PHP Framework
相关推荐
Yii2 RESTful API 响应格式化指南:内容协商、Serializer 序列化与 JSON/XML 输出控制
Yii2 RESTful API 响应格式化指南:内容协商、Serializer 序列化与 JSON/XML 输出控制 导读 在 Yii2 中,一次 RESTf
后端Web框架chatgpt-java响应格式定制:ResponseFormat与JSON输出控制
chatgpt java响应格式定制:ResponseFormat与JSON输出控制 在集成ChatGPT API时,你是否遇到过AI返回格式混乱导致解析失败的
Autoformer未来展望:从Nature Machine Intelligence到下一代时间序列AI
Autoformer未来展望:从Nature Machine Intelligence到下一代时间序列AI Autoformer作为NeurIPS 2021的创
人工智能深度学习机器学习
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考