波浪号约束表示允许升级到下一个不兼容的最小主版本号之前的所有版本,如~2.3.0等价于>=2.3.0 <3.0.0,~1.2等价于>=1.2.0 <2.0.0,~0.2.3等价于>=0.2.3 <0.3.0。

composer.json 里 ~ 波浪号版本约束到底怎么算
它不是“取最新小版本”,而是“允许升级到下一个**不兼容的最小主版本号之前**的所有版本”。比如 ~2.3.0 等价于 >=2.3.0 ,不是 <code>>=2.3.0 。
常见误解是以为 ~2.3 只放行 2.3.x,其实 Composer 会自动补零:没有补全的版本(如 2.3)会被当成 2.3.0 处理,所以 ~2.3 和 ~2.3.0 完全等价。
-
~1.2→>=1.2.0 -
~1.2.3→>=1.2.3 -
~0.2.3→>=0.2.3 (注意:0.x 是特例,<code> 而非 <code>)
什么时候该用 ~,什么时候该用 ^
^ 是默认推荐的约束方式,语义更稳定;~ 更激进,适合你明确知道某次小版本更新不会破坏 API,且想尽快吃到修复补丁的场景。
比如你在维护一个内部工具库,v2.3.1 修了一个你正在踩的 bug,但 v2.4.0 还没发布、也不确定会不会改接口,这时写 "my/tool": "~2.3.1" 就能自动拉到 2.3.2、2.3.9,但卡死在 2.4.0 之前。
- 用
^:绝大多数项目依赖,遵循 SemVer,安全兜底 - 用
~:明确控制“只允许 patch 升级”,且主/次版本号意义清晰(比如~3.1.0表示“只要还是 3.1.x 就行”) - 避免
~用于0.x版本以外的模糊写法,比如~2会被解释为>=2.0.0 ,容易误判范围
composer update 不按 ~ 约束升级?检查这三处
波浪号约束只在安装或更新时起作用,但实际行为受本地锁文件、全局配置和仓库响应影响。最常踩的坑不是写错了,而是没刷新上下文。
- 删掉
composer.lock再composer install—— 锁文件会固化旧版本,覆盖composer.json的约束 - 确认
config.platform没强行降级 PHP 或扩展版本,导致某些新版包被跳过 - 运行
composer show vendor/package查当前装的是哪个版本,再对比composer why-not vendor/package:2.3.5看为什么没升上去(比如某个间接依赖锁死了低版本)
版本约束写错的典型错误现象
最直接的表现是 composer require 报错、composer update 没反应、或者 CI 构建突然失败——往往是因为约束太松或太紧,触发了意料外的版本解析。
-
"monolog/monolog": "~2.0.0"→ 实际允许2.9.9,但如果你只测试过2.0.2,上线后可能因2.8.0的日志格式变更出问题 -
"php": "~8.1"→ 等价于>=8.1.0 ,但某些扩展(如 <code>ext-gd)在8.1.25才修复内存泄漏,而你的环境是8.1.0,结果跑着跑着 OOM - 混用
^和~在同一项目中,比如^2.0和~2.0.0看似一样,但前者允许2.9.9,后者也允许2.9.9—— 这里没区别;真正危险的是~2.0和~2.0.0其实一样,但人眼容易看漏末尾的.0,误以为是“只到 2.0.x”
版本约束不是写完就完事,它和 lock 文件、PHP 环境、依赖图深度耦合。哪怕一个 ~ 写对了,如果 vendor/autoload.php 被提前加载、或者 composer dump-autoload -o 没重生成,照样会跑错版本的代码。










