Yii2 RESTful 中 Accept 头生效需显式配置 ContentNegotiator 行为,启用 acceptHeader、清空 formats、注册自定义 media type 到 response.formatters,并统一在 beforeAction 中按 URL 版本 > Accept 头 > 默认版本的优先级解析版本。

Yii2 RESTful 里怎么让 Accept 头真正生效?
Yii2 默认不校验 Accept 头,哪怕你传了 application/vnd.myapp.v2+json,它照样走默认版本。关键在 ContentNegotiator 行为没配对,或者配了但没启用 acceptHeader 模式。
- 必须在控制器里显式挂载
ContentNegotiator行为,并设'formats' => [](清空默认格式映射),否则 Yii 会优先按后缀(如.json)匹配,忽略Accept -
acceptHeader要设为true,且formatParam别和acceptHeader冲突——比如同时开了formatParam(如format=json),请求带Accept反而会被覆盖 - 自定义 media type 必须注册到
Response::formatters,否则即使头匹配了,JsonResponseFormatter也认不出你的vnd.myapp.v2+json
怎么注册自定义 MediaType(比如 vnd.myapp.v2+json)?
Yii2 的 Response 不认识你造的 media type,得手动告诉它:这个 type 对应哪个 formatter、用什么 mime 类型解析。
- 在
config/web.php或应用启动时,往response.formatters加一项:'application/vnd.myapp.v2+json' => 'yii\web\JsonResponseFormatter' - 别漏掉
response.format = 'json',否则 formatter 不会触发;如果要支持多版本差异化序列化,得自己写子类继承JsonResponseFormatter,重写format方法判断$response->getHeaders()->get('Accept') - 注意 mime type 字符串必须完全一致——
vnd.myapp.v2+json和vnd.myapp.v2+json; charset=UTF-8是两个不同 type,后者不会命中注册项
路由层怎么按版本分发请求(比如 /api/v2/users)?
单纯靠 URL 路径分版本最直接,但容易和 Accept 头逻辑打架。Yii2 的 UrlRule 支持正则捕获,可把版本号转成参数透传给控制器。
- 在
urlManager.rules里加一条:'pattern' => 'api/v<code>version:\d+/<action:\w+>', 'route' => 'api/<action>', 'defaults' => ['version' => '1'] - 控制器里通过
$this->module->params['version']或Yii::$app->request->get('version')拿版本号,再决定调用哪个 service 层或 serializer - 别在路由规则里硬编码
v1/v2多条规则——维护成本高,且无法 fallback;用正则捕获 + 默认值更灵活
Accept 头和 URL 版本同时存在时,以谁为准?
没有默认规则,全靠你自己定优先级。实际项目中建议明确约定:URL 路径版本 > Accept 头 > 默认版本。否则前端传错头、后端又没校验,很容易出现 v2 接口返回 v1 数据这种静默错误。
- 在
beforeAction()里统一提取版本信息:先从路由参数取version,有则直接用;没有再 parseAccept头;都无则 fallback - 特别注意
Accept头可能带多个 type,如Accept: application/vnd.myapp.v2+json, application/json,要用yii\web\AcceptHeaderParser解析权重,别简单strpos匹配 - 版本号建议只接受数字(
\d+),拒绝v2.1或beta这类非规范值——避免后续兼容性爆炸
版本控制最难的不是怎么写,而是所有入口(路由、头、参数、甚至 query string)都要收敛到同一个版本决策点,漏一个就可能绕过版本逻辑。别信“只要 Accept 头对就行”这种直觉。



















