news 2026/9/24 14:51:31

Yii 2 RESTful 响应格式详解:内容协商、数据序列化与 JSON 输出控制

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Yii 2 RESTful 响应格式详解:内容协商、数据序列化与 JSON 输出控制
  • 后端
  • Web框架

【免费下载链接】yii2

Yii 2: The Fast, Secure and Professional PHP Framework

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

本篇技术指南聚焦 Yii 2 框架中 RESTful API 的响应格式处理机制,围绕官方指南 rest-response-formatting.md 展开,完整覆盖"内容协商(Content Negotiation)→ 数据序列化(Serializer)→ 响应格式化(Response Formatter)"的三阶段管线,并结合仓库源码(framework/rest、framework/web)深入解析ContentNegotiatorSerializerJsonResponseFormatter等核心类的底层实现。读完本文,你将能够精确控制 RESTful API 的输出格式、分页信封结构、JSON 编码选项,并理解 DAO 与 ActiveRecord 在数据类型转换上的差异。

RESTful API 响应格式的三阶段管线

当 Yii 2 处理一个 RESTful API 请求时,响应格式的产生遵循如下步骤(见 Controller.php 中的请求处理周期注释):

  1. 确定影响响应格式的因素:媒介类型(MIME type)、语言、版本等,这一过程称为内容协商(content negotiation),由yii\filters\ContentNegotiator过滤器完成。
  2. 将资源对象转换为数组:资源对象(实现yii\base\ArrayableInterface)与资源集合(实现yii\data\DataProviderInterface)通过yii\rest\Serializer序列化为 PHP 数组,细节可参考 Resources(资源)。
  3. 将数组转换为字符串:通过注册在yii\web\Response应用组件formatters属性中的yii\web\ResponseFormatterInterface响应格式化器,把数组序列化为 JSON、XML 等字符串并写入响应主体。

这条管线在yii\rest\Controller::afterAction()中闭环:动作执行返回资源对象或集合后,serializeData() 通过Yii::createObject($this->serializer)->serialize($data)触发序列化,随后Response组件在prepare()阶段调用已选定的 formatter 完成最终输出(见 Response.php)。

内容协商:ContentNegotiator 过滤器

Yii 通过yii\filters\ContentNegotiator过滤器提供内容协商支持。RESTful API 基于控制器基类yii\rest\ControllercontentNegotiator行为中内置了这个过滤器(见 Controller.php)。

该过滤器同时支持响应格式协商应用语言协商两个维度(见 ContentNegotiator.php):

  • 配置formats属性时,根据 GET 参数_formatAcceptHTTP 头协商响应格式,命中后设置Response::format,并同步更新Response::acceptMimeTypeacceptParams
  • 配置languages属性时,根据 GET 参数_langAccept-LanguageHTTP 头协商应用语言,命中后设置Yii::$app->language

ContentNegotiator继承自ActionFilter并实现BootstrapInterface,因此它既能作为控制器/模块的动作过滤器(通过behaviors()声明,作用于局部控制器或指定动作),也能作为应用级 bootstrap 组件(作用于整个应用),对应实现为beforeAction()bootstrap()两个入口(ContentNegotiator.php)。

Accept 头驱动的格式选择

当 RESTful API 请求携带如下 header 时:

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

协商结果为 JSON 格式响应,curl -i观察到的完整响应如下:

$ 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, ... }, ... ]

响应中的X-Pagination-*头与Link头由序列化器在分页场景下自动写入(见下文"数据序列化")。

协商过程的源码级拆解

幕后流程如下:在执行 RESTful API 控制器动作之前,ContentNegotiator检查请求的AcceptHTTP 头,并将yii\web\Response::format配置为'json';动作执行并返回资源对象或集合后,Serializer将结果转换为数组;最后由JsonResponseFormatter将数组序列化为 JSON 字符串并放入响应主体。

negotiateContentType()(ContentNegotiator.php)的具体协商逻辑为:

  1. 若配置了formatParam(默认_format)且请求携带该 GET 参数:直接校验该格式是否存在于formats中——存在则立即采用;若参数为数组则抛出BadRequestHttpException(400);格式不受支持则抛出NotAcceptableHttpException(406)。
  2. 否则遍历Request::getAcceptableContentTypes()返回的可接受类型(按 q 值排序):首个命中formats键的 MIME 类型即被选中。
  3. 若没有匹配但请求包含*/*通配,则使用formats中声明的第一个格式兜底。
  4. 若请求的媒介类型均不被支持,抛出NotAcceptableHttpException(406)。

此外,当formats中声明了多种格式(或languages中声明了多种语言)时,协商会自动向响应添加Vary: Accept(或Vary: Accept-Language)头(见 negotiate()),这对 HTTP 缓存层的正确工作至关重要。

扩展新的响应格式

默认情况下,RESTful API 同时支持 JSON 和 XML 两种格式(对应yii\rest\Controller::behaviors()中的'application/json' => Response::FORMAT_JSON'application/xml' => Response::FORMAT_XML)。要支持新的格式,需在contentNegotiator过滤器中配置yii\filters\ContentNegotiator::formats属性,例如在 API 控制器类中新增 HTML 支持:

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组件默认注册的格式化器见 defaultFormatters():

格式常量默认格式化器
Response::FORMAT_HTMLhtmlyii\web\HtmlResponseFormatter
Response::FORMAT_XMLxmlyii\web\XmlResponseFormatter
Response::FORMAT_JSONjsonyii\web\JsonResponseFormatter
Response::FORMAT_JSONPjsonpyii\web\JsonResponseFormatter(启用useJsonp

FORMAT_RAW不经过格式化器,直接以原始数据作为响应内容。在 Response::prepare() 中,若formatters[$format]不是已实例化对象,会通过Yii::createObject()惰性创建,并要求其实例必须实现ResponseFormatterInterface,否则抛出InvalidConfigException

数据序列化:Serializer

yii\rest\Serializer负责将资源对象或集合转换为数组:它把实现yii\base\ArrayableInterface的对象(前者主要由资源对象实现,即yii\base\Model及其子类,如yii\db\ActiveRecord)和实现yii\data\DataProviderInterface的对象(资源集合)统一序列化。

序列化的类型分派

Serializer::serialize()(Serializer.php)按以下优先级分派处理:

  1. 带验证错误的ModelserializeModelErrors():将响应状态码设为 422("Data Validation Failed."),并输出形如[['field' => ..., 'message' => ...], ...]的错误数组(Serializer.php);
  2. 实现Arrayable的对象serializeModel():结合fields/expand请求参数调用$model->toArray($fields, $expand)
  3. 实现JsonSerializable的对象→ 直接调用jsonSerialize()
  4. 实现DataProviderInterface的对象serializeDataProvider(),处理集合与分页;
  5. 普通数组→ 递归地对每个元素执行上述分派;
  6. 其他类型原样返回。

单资源序列化与字段筛选

serializeModel()(Serializer.php)通过getRequestedFields()从请求参数读取字段选择信息:

  • fieldsParam(默认fields):逗号分隔的字段列表,对应Model::fields()中声明的默认字段;
  • expandParam(默认expand):逗号分隔的额外字段列表,对应Model::extraFields()中声明的扩展字段。

例如GET /users?fields=id,email只返回idemailGET /users?expand=profile则追加profile字段;嵌套展开(如expand=post.author)也受支持,详见 rest-resources.md。

集合序列化与分页头

serializeDataProvider()(Serializer.php)是集合处理的核心:

  1. preserveKeysfalse(默认),通过array_values()重新索引模型数组;为true时保留原始数组键,可输出带键索引的 JSON 对象;
  2. 逐个序列化模型(serializeModels()Arrayable对象调用serializeModel(),对普通数组调用ArrayHelper::toArray());
  3. 若数据提供器启用了分页,调用addPaginationHeaders()写入分页相关 HTTP 头;
  4. HEAD 请求直接返回null(响应主体为空);
  5. 未配置collectionEnvelope时,直接返回模型数组。

addPaginationHeaders()(Serializer.php)负责向响应写入分页信息,对应上文响应示例中的各头:

头名称含义
X-Pagination-Total-Count数据总条数
X-Pagination-Page-Count总页数
X-Pagination-Current-Page当前页码(从 1 开始)
X-Pagination-Per-Page每页条数
Linkselfnextlast等分页链接(<url>; rel=rel格式)

使用 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 } }

该响应结构的生成逻辑在 serializeDataProvider() 与 serializePagination() 中:_links通过Link::serialize($pagination->getLinks(true))生成,_metaPagination::toArray()语义的四个键构成。信封名称均可定制:linksEnvelope(默认_links)与metaEnvelope(默认_meta)属性自 2.0.4 起可用。不设置collectionEnvelope时,分页信息仅通过 HTTP 头暴露,集合直接输出为数组。

控制 JSON 输出:JsonResponseFormatter

JSON 响应由yii\web\JsonResponseFormatter生成,内部使用yii\helpers\Json(即BaseJson)进行编码。该格式化器提供以下可配置项(见 JsonResponseFormatter.php):

  • prettyPrint(默认false):是否输出人类可读的"美化" JSON。启用时向encodeOptions追加JSON_PRETTY_PRINT,在开发调试阶段非常实用;
  • encodeOptions(默认JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE,即数值 320):传递给Json::encode()json_encode()选项,用于控制 JSON 编码行为(如转义规则、数值格式化等);
  • useJsonp(默认false):是否使用 JSONP 输出格式。启用时要求响应数据为包含datacallback两个键的数组,输出形如callback(data);Content-Type变为application/javascript; charset=UTF-8
  • contentType(默认null):自定义Content-Type头;为null时依据useJsonp自动选择application/jsonapplication/javascript
  • keepObjectType(默认跟随Json::$keepObjectType):避免零索引键对象被编码为数组,与原生json_encode()行为对齐(2.0.44 起可用)。

format()方法(JsonResponseFormatter.php)先设置Content-Type头,再根据useJsonp分发到formatJsonp()formatJson()。其中formatJson()prettyPrint为真时通过位或运算追加JSON_PRETTY_PRINT,并调用Json::encode($response->data, $options)生成内容(JsonResponseFormatter.php);若数据为null且无内容,则输出字符串'null'

在 response 组件中配置格式化器

格式化器在应用配置的response组件formatters属性中配置(配置机制详见 概念-配置),如下所示:

'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, // ... ], ], ],

配置数组的键是格式名(如json),值是创建格式化器对象的配置。Response::init()中通过array_merge($this->defaultFormatters(), $this->formatters)将自定义配置与默认格式化器合并(Response.php),因此这里的键json会覆盖默认的JsonResponseFormatter配置。encodeOptions支持 PHP 原生json_encode()的全部选项常量,例如JSON_UNESCAPED_SLASHES(不转义斜杠)、JSON_UNESCAPED_UNICODE(不转义 Unicode 字符)、JSON_NUMERIC_CHECK(数字字符串转数字)、JSON_PRETTY_PRINT(美化输出)等,可按位或组合使用。

JSONP 输出

当需要跨域场景(不使用 CORS)时,可将格式配置为Response::FORMAT_JSONP,并在控制器中返回['data' => $data, 'callback' => $callbackName]结构。formatJsonp()(JsonResponseFormatter.php)会输出callback(data);形式的 JavaScript 代码,其中数据经Json::htmlEncode()处理以确保 HTML 安全;若数据缺少data/callback键,则记录一条 warning 并输出空内容。

数据类型一致性:DAO 与 ActiveRecord 的差异

当使用 DAO(数据库访问层) 从数据库返回数据时,所有数据都会表示成字符串(数据库驱动原生返回的裸类型),这不总是符合预期——尤其是数值列在 JSON 中本应表现为数字(如"id": 1而非"id": "1")时。

而当使用 ActiveRecord 层从数据库检索数据时,在yii\db\ActiveRecord::populateRecord()中填充数据的过程中,数字列的值会被转换为整数(如intval),从而在 JSON 输出中呈现为 JSON 数字而非字符串。因此,若你的 RESTful API 完全基于 ActiveRecord 构建,JSON 中的数值类型通常是正确的;若混用或直接使用 DAO 查询,需要注意对返回的标量类型做显式转换,或在encodeOptions中使用JSON_NUMERIC_CHECKjson_encode()自动将数字字符串编码为 JSON 数字(需结合数据语义谨慎使用)。

测试验证

仓库测试目录提供了与本文主题直接对应的测试用例,可作为理解行为边界的参考:

  • tests/framework/rest/SerializerTest.php:覆盖Serializer的模型错误序列化(422 状态码与错误结构)、serialize()类型分派、分页头写入、collectionEnvelope信封输出等场景;
  • tests/framework/filters/ContentNegotiatorTest.php:覆盖ContentNegotiatorAccept头解析、_formatGET 参数、格式不可接受时抛出NotAcceptableHttpException等行为。

小结

Yii 2 将 RESTful API 响应格式处理拆解为职责清晰的三层:ContentNegotiator负责基于Accept头与_format参数完成格式/语言协商并设置Response::formatSerializer负责将资源对象与数据提供器转换为数组,并负责分页头与信封结构;Response::formatters中注册的ResponseFormatterInterface实现(默认含 HTML/XML/JSON/JSONP 四种)负责最终字符串化。理解这三层各自的职责边界与可配置属性,即可对 API 输出的媒介类型、字段筛选、分页结构、JSON 编码细节实现全面掌控。

  • 后端
  • Web框架

【免费下载链接】yii2

Yii 2: The Fast, Secure and Professional PHP Framework

项目地址:https://gitcode.com/gh_mirrors/yi/yii2
点击查看免费下载
上一篇:腾讯混元小模型全系列部署详解:从0.5B到7B的本地化落地指南
下一篇:Xray编辑器配色方案:从CSS变量到主题切换动画实现

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/24 14:49:43

Argos Translate:一条命令安装,快速上手离线多语言翻译

Argos Translate&#xff1a;一条命令安装&#xff0c;快速上手离线多语言翻译 【免费下载链接】argos-translate Open-source offline translation library written in Python 项目地址: https://gitcode.com/GitHub_Trending/ar/argos-translate Argos Translate 是一…

作者头像 李华
网站建设 2026/9/24 14:48:15

Zonotope几何建模:虚拟电厂分布式资源不确定性聚合方法

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华