第三章 版本约束的艺术:精确版本、范围约束、通配符、波浪号~与脱字符^的区别

版本约束这东西,说白了就是告诉 Composer:「你要装哪个版本的包,以及能容忍多大的变化。」

我刚用 Composer 那会儿,觉得随便写个 "monolog/monolog": "1.0" 就行了。结果呢?某天同事更新了依赖,整个项目炸了。嗯,从那以后我才认真研究版本约束的细节。

今天咱们就把这几种约束方式掰开揉碎,讲清楚。

一、精确版本:最保守的选择

写法很简单:

"monolog/monolog": "1.25.3"

意思就是:我只要 1.25.3 这个版本,多一个小版本号都不行。

什么时候用?

  • 你明确知道某个版本是稳定的
  • 你依赖的包 API 经常变,不敢乱升级
  • 生产环境需要锁定版本
注意: 精确版本太死板。如果这个包有安全更新,你只能手动改 composer.json。我有个项目就因为忘了更新,被安全扫描工具报了漏洞。

二、范围约束:给版本划个区间

用比较运算符来限定范围:

"vendor/package": ">=1.0 <2.0"
"vendor/package": ">=1.0, <2.0"   // 逗号也行

还可以组合:

"vendor/package": ">=1.0, <1.1 || >=1.2"

这表示:1.0 到 1.1 之间(不含 1.1),或者 1.2 及以上。为什么跳过 1.1?因为那个版本有 bug。

我个人习惯用范围约束来「围堵」有问题的版本。比如某个包 2.3.0 有性能问题,我就写:

"vendor/package": ">=2.0, <2.3.0 || >=2.3.1"

三、通配符:偷懒但有效

"monolog/monolog": "1.*"

这表示:1.0、1.1、1.2……只要是 1.x 都行。相当于 >=1.0, <2.0

也可以更细:

"monolog/monolog": "1.2.*"

只允许 1.2.x 系列。

通配符看着方便,但有个坑:它允许小版本升级。如果包作者在 1.2.5 里改了公共方法签名,你的代码可能就挂了。

我的建议: 通配符适合快速开发阶段。上线前最好换成更严格的约束。

四、波浪号 ~:锁定主版本和次版本

这是很多人容易搞混的地方。咱们直接看例子:

写法 等价范围 允许的变化
~1.2 >=1.2, <2.0 允许小版本升级(1.2 → 1.3)
~1.2.3 >=1.2.3, <1.3 只允许补丁版本升级(1.2.3 → 1.2.4)

说白了,波浪号的意思是:「别给我乱升大版本,小版本可以动,但别动次版本号。」

我记得有一次,团队里有人写了 ~2.0,结果 Composer 直接装了 2.9.9。他吓一跳,以为要出大事。其实没问题,因为 2.x 之间是向后兼容的——至少理论上如此。

五、脱字符 ^:最推荐的写法

这是 Composer 官方最推荐的方式。看例子:

写法 等价范围 允许的变化
^1.2.3 >=1.2.3, <2.0 允许次版本和补丁版本升级
^0.3 >=0.3.0, <0.4.0 0.x 版本特殊处理,只允许补丁升级
^0.0.3 >=0.0.3, <0.0.4 连补丁都不能升

脱字符的意思是:「我相信语义化版本。只要主版本不变,次版本和补丁版本应该都是向后兼容的。」

你想想看,如果每个包作者都遵守语义化版本规范,那 ^1.2.3 就是最省心的写法。既能拿到 bug 修复(补丁版本),也能拿到新功能(次版本),还不用担心 API 被改。

核心区别总结:
  • ~ 更保守:锁定次版本,只允许补丁升级
  • ^ 更开放:锁定主版本,允许次版本和补丁升级

六、实战中的选择策略

我一般这样选:

  1. 新项目、新依赖:用 ^。比如 "laravel/framework": "^8.0"。这样能自动获取小版本更新。
  2. 对稳定性要求极高:用 ~ 加三位版本号。比如 "monolog/monolog": "~2.3.0"。只接受补丁更新。
  3. 有已知问题的版本:用范围约束排除。比如 "vendor/package": "^2.0, !=2.1.5"
  4. 锁定生产环境:用 composer.lock 文件。composer.json 里可以宽松些,但 lock 文件保证所有人装的一样。
避坑指南: 我曾经在 ^0.9 上吃过亏。0.x 版本在语义化版本里不保证稳定性,结果 0.10 直接改了接口。所以对于 0.x 的包,我建议用精确版本或者 ~ 约束。

七、一张表看懂所有约束

写法 含义 示例匹配 不匹配
1.2.3 精确版本 1.2.3 1.2.4, 1.3.0
>=1.2 大于等于 1.2, 2.0, 3.5 1.1.9
1.* 通配符 1.0, 1.9.9 2.0
~1.2.3 波浪号 1.2.3, 1.2.9 1.3.0, 2.0
^1.2.3 脱字符 1.2.3, 1.9.9 2.0, 0.9.0

八、我的个人建议

如果你问我,新手最该记住什么?

记住两点:

  • 大部分时候用 ^,它最符合语义化版本的精神
  • 遇到 0.x 的包,换成 ~ 或者精确版本

至于通配符和范围约束,偶尔用用就好。它们太灵活,容易让依赖关系变得不可控。

嗯,版本约束这块就讲这么多。下一章咱们聊聊 composer.lock 文件——那个让团队保持一致的秘密武器。