- 后端
- Web框架
【免费下载链接】yii2
Yii 2: The Fast, Secure and Professional PHP Framework
本篇技术指南聚焦 Yii 2 框架中 RESTful API 的响应格式处理机制,围绕官方指南 rest-response-formatting.md 展开,完整覆盖"内容协商(Content Negotiation)→ 数据序列化(Serializer)→ 响应格式化(Response Formatter)"的三阶段管线,并结合仓库源码(framework/rest、framework/web)深入解析ContentNegotiator、Serializer、JsonResponseFormatter等核心类的底层实现。读完本文,你将能够精确控制 RESTful API 的输出格式、分页信封结构、JSON 编码选项,并理解 DAO 与 ActiveRecord 在数据类型转换上的差异。
RESTful API 响应格式的三阶段管线
当 Yii 2 处理一个 RESTful API 请求时,响应格式的产生遵循如下步骤(见 Controller.php 中的请求处理周期注释):
- 确定影响响应格式的因素:媒介类型(MIME type)、语言、版本等,这一过程称为内容协商(content negotiation),由
yii\filters\ContentNegotiator过滤器完成。 - 将资源对象转换为数组:资源对象(实现
yii\base\ArrayableInterface)与资源集合(实现yii\data\DataProviderInterface)通过yii\rest\Serializer序列化为 PHP 数组,细节可参考 Resources(资源)。 - 将数组转换为字符串:通过注册在
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\Controller在contentNegotiator行为中内置了这个过滤器(见 Controller.php)。
该过滤器同时支持响应格式协商与应用语言协商两个维度(见 ContentNegotiator.php):
- 配置
formats属性时,根据 GET 参数_format与AcceptHTTP 头协商响应格式,命中后设置Response::format,并同步更新Response::acceptMimeType与acceptParams; - 配置
languages属性时,根据 GET 参数_lang与Accept-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)的具体协商逻辑为:
- 若配置了
formatParam(默认_format)且请求携带该 GET 参数:直接校验该格式是否存在于formats中——存在则立即采用;若参数为数组则抛出BadRequestHttpException(400);格式不受支持则抛出NotAcceptableHttpException(406)。 - 否则遍历
Request::getAcceptableContentTypes()返回的可接受类型(按 q 值排序):首个命中formats键的 MIME 类型即被选中。 - 若没有匹配但请求包含
*/*通配,则使用formats中声明的第一个格式兜底。 - 若请求的媒介类型均不被支持,抛出
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_HTML | html | yii\web\HtmlResponseFormatter |
Response::FORMAT_XML | xml | yii\web\XmlResponseFormatter |
Response::FORMAT_JSON | json | yii\web\JsonResponseFormatter |
Response::FORMAT_JSONP | jsonp | yii\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)按以下优先级分派处理:
- 带验证错误的
Model→serializeModelErrors():将响应状态码设为 422("Data Validation Failed."),并输出形如[['field' => ..., 'message' => ...], ...]的错误数组(Serializer.php); - 实现
Arrayable的对象→serializeModel():结合fields/expand请求参数调用$model->toArray($fields, $expand); - 实现
JsonSerializable的对象→ 直接调用jsonSerialize(); - 实现
DataProviderInterface的对象→serializeDataProvider(),处理集合与分页; - 普通数组→ 递归地对每个元素执行上述分派;
- 其他类型原样返回。
单资源序列化与字段筛选
serializeModel()(Serializer.php)通过getRequestedFields()从请求参数读取字段选择信息:
fieldsParam(默认fields):逗号分隔的字段列表,对应Model::fields()中声明的默认字段;expandParam(默认expand):逗号分隔的额外字段列表,对应Model::extraFields()中声明的扩展字段。
例如GET /users?fields=id,email只返回id与email,GET /users?expand=profile则追加profile字段;嵌套展开(如expand=post.author)也受支持,详见 rest-resources.md。
集合序列化与分页头
serializeDataProvider()(Serializer.php)是集合处理的核心:
- 若
preserveKeys为false(默认),通过array_values()重新索引模型数组;为true时保留原始数组键,可输出带键索引的 JSON 对象; - 逐个序列化模型(
serializeModels()对Arrayable对象调用serializeModel(),对普通数组调用ArrayHelper::toArray()); - 若数据提供器启用了分页,调用
addPaginationHeaders()写入分页相关 HTTP 头; - HEAD 请求直接返回
null(响应主体为空); - 未配置
collectionEnvelope时,直接返回模型数组。
addPaginationHeaders()(Serializer.php)负责向响应写入分页信息,对应上文响应示例中的各头:
| 头名称 | 含义 |
|---|---|
X-Pagination-Total-Count | 数据总条数 |
X-Pagination-Page-Count | 总页数 |
X-Pagination-Current-Page | 当前页码(从 1 开始) |
X-Pagination-Per-Page | 每页条数 |
Link | self、next、last等分页链接(<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))生成,_meta由Pagination::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 输出格式。启用时要求响应数据为包含data与callback两个键的数组,输出形如callback(data);,Content-Type变为application/javascript; charset=UTF-8;contentType(默认null):自定义Content-Type头;为null时依据useJsonp自动选择application/json或application/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_CHECK让json_encode()自动将数字字符串编码为 JSON 数字(需结合数据语义谨慎使用)。
测试验证
仓库测试目录提供了与本文主题直接对应的测试用例,可作为理解行为边界的参考:
- tests/framework/rest/SerializerTest.php:覆盖
Serializer的模型错误序列化(422 状态码与错误结构)、serialize()类型分派、分页头写入、collectionEnvelope信封输出等场景; - tests/framework/filters/ContentNegotiatorTest.php:覆盖
ContentNegotiator的Accept头解析、_formatGET 参数、格式不可接受时抛出NotAcceptableHttpException等行为。
小结
Yii 2 将 RESTful API 响应格式处理拆解为职责清晰的三层:ContentNegotiator负责基于Accept头与_format参数完成格式/语言协商并设置Response::format;Serializer负责将资源对象与数据提供器转换为数组,并负责分页头与信封结构;Response::formatters中注册的ResponseFormatterInterface实现(默认含 HTML/XML/JSON/JSONP 四种)负责最终字符串化。理解这三层各自的职责边界与可配置属性,即可对 API 输出的媒介类型、字段筛选、分页结构、JSON 编码细节实现全面掌控。
- 后端
- Web框架
【免费下载链接】yii2
Yii 2: The Fast, Secure and Professional PHP Framework
相关推荐
Yii 2 RESTful API 响应格式配置实战:内容协商、数据序列化与 JSON 输出控制
Yii 2 RESTful API 响应格式配置实战:内容协商、数据序列化与 JSON 输出控制 RESTful API 的响应格式决定了客户端拿到的是 JSO
后端Web框架Yii 2 RESTful API 响应格式化完全指南:内容协商、数据序列化与 JSON 输出控制
Yii 2 RESTful API 响应格式化完全指南:内容协商、数据序列化与 JSON 输出控制 在 Yii 2 框架中,RESTful API 的响应格式化
后端Web框架Yii2 RESTful API 响应格式化指南:内容协商、Serializer 序列化与 JSON/XML 输出控制
Yii2 RESTful API 响应格式化指南:内容协商、Serializer 序列化与 JSON/XML 输出控制 导读 在 Yii2 中,一次 RESTf
后端Web框架
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考