Yii RESTful版本控制核心是Module+UrlRule+ContentNegotiator协同:v1/v2须物理隔离模块、独立命名空间与配置,路由用正则透传版本,Accept头需启用ContentNegotiator并清空formats,media type须精确注册,版本解析统一收口beforeAction,model/serializer严禁跨版本共享。

Yii 框架做 RESTful 接口版本控制,核心不是靠“插件”或“中间件”,而是靠 Module + UrlRule + ContentNegotiator 三者协同。v1 和 v2 不是同一套代码打补丁,而是物理隔离的模块,否则后期维护会失控。
模块必须独立定义,不能共用 controllerNamespace
很多人把 v1 和 v2 放在同一个 controllers 目录下,仅靠判断 $version 参数走不同逻辑——这会导致测试难、路由混乱、IDE 跳转失效。
-
api/modules/v1/Module.php中的$controllerNamespace必须明确为'api\modules\v1\controllers',不能写成'api\controllers' -
v2同理,路径、命名空间、配置项全部独立,哪怕两个控制器类名一样(如UserController),也必须放在各自模块目录下 - 模块类里
init()方法中不要手动加载v2的 service 或 model,否则破坏隔离性
URL 路由规则要捕获版本号并透传,别硬编码多条 rule
在 common/config/main.php 的 urlManager.rules 里写死 'GET api/v1/users' => 'v1/user/index' 和 'GET api/v2/users' => 'v2/user/index' 是错的——新增版本就得改配置,且无法 fallback。
- 用正则规则:
'<version:>/<controller:>/<action:>' => '<version>/<controller>/<action>'</action></controller></version></action:></controller:></version:> - 确保
urlManager.enablePrettyUrl = true,否则/v1/users会被当成 pathinfo 处理失败 - 模块配置里
modules数组必须包含'v1' => ['class' => 'api\modules\v1\Module'],否则路由匹配成功但找不到模块
Accept 头生效的前提是 ContentNegotiator 行为被正确挂载
即使你写了 application/vnd.myapp.v2+json,Yii 默认也完全无视它——因为 ContentNegotiator 没启用 acceptHeader 模式,或者没清空 formats。
- 在控制器基类(如
api\modules\v1\controllers\BaseController)中挂载行为:'class' => 'yii\filters\ContentNegotiator', 'formats' => [], 'acceptHeader' => true -
formats => []是关键,不加这句,Yii 会优先按.json后缀匹配,直接跳过 Accept 头解析 - 自定义 media type(如
vnd.myapp.v2+json)必须注册到response.formatters,且 key 必须和请求头中完全一致(包括大小写、有无空格) - 注册方式:
'application/vnd.myapp.v2+json' => 'yii\web\JsonResponseFormatter',写在api/config/main.php的components.response.formatters里
版本解析逻辑必须统一收口,避免散落在各 action 中
别在每个 actionIndex() 里重复写 if ($version === 'v2') { ... }——容易漏判、难测、违反单一职责。
- 在模块的
beforeAction()中统一解析:$version = Yii::$app->request->get('version', Yii::$app->request->get('v', 'v1')) - 把解析结果存到
Yii::$app->params['apiVersion']或注入到当前模块实例属性中 - 后续 controller/service 层只读这个值,不重新解析;若需 fallback 到默认版本,就在这里统一处理,而不是让下游报错
- 注意:URL 路径中的
v1是路由参数,不是 query string,得通过Yii::$app->requestedRoute或UrlRule解析后的参数获取,不是get()
最常被忽略的一点:模块间 model 和 serializer 不能共享引用。比如 v2 返回字段加了 updated_at_formatted,但用的还是 v1 的 fields() 方法——这不是版本控制,是埋雷。


















