^1.2.3 等价于 >=1.2.3 且 =1.2.3 且 =1.2.0 且 =1.2.0 且 =1.0.0 且 =1.0.0 且

^ 和 ~ 的数学边界到底是什么
它们不是“大概兼容”或“差不多就行”,而是有明确不等式定义的版本区间。写错一个符号,composer update 就可能装上你完全没测过的版本。
^1.2.3 等价于 >=1.2.3 ;~1.2.3 等价于 >=1.2.3 。关键区别在上限:^ 放开整个次版本段(1.x),~ 只放开补丁段(1.2.x)。
-
^2.0.0允许升到2.9.9,但拒绝3.0.0 -
~2.0.0最多升到2.0.99,连2.1.0都被拦住 -
^0.8.2实际等价于~0.8.2(因为 0.x 阶段 ^ 自动降级为 ~ 级别)
什么时候该用 ~ 而不是 ^
不是“保守就选 ~”,而是当你明确知道某个 minor 版本里存在不可替代的行为——比如某支付 SDK 的 1.2.x 才支持某银行的特定签名头,而 1.3.0 已悄悄删掉它。
- 对接硬件驱动、政府接口、银行中间件等更新慢且文档缺失的包
- 项目卡在某个 minor 版本的 bug 修复节奏里(如
guzzlehttp/guzzle的7.5.x修复了你依赖的 DNS timeout 问题) - CI 构建要求可重复性极高,且你已对
1.2.7做过全链路压测
别把 ~1.2 当成“稳一点的 ^1.2”——它和 ~1.2.0 不同,等价于 >=1.2.0 ,范围反而更大。
写死版本(无符号)的真实代价
直接写 "monolog/monolog": "2.8.0" 看似可控,实则主动放弃所有后续补丁更新,包括安全修复。
- 它等价于
==2.8.0,连2.8.1都不会装 - 如果你没提交
composer.lock,不同人composer install可能装出不同版本(因 Packagist 上的可用版本随时间变化) - 更稳妥的做法是:用
"2.8.*"(等价于>=2.8.0 )+ 提交 lock 文件
为什么改了 composer.json 还不生效
composer.json 中的约束只在 composer update 或首次 composer install 时起作用。已存在的 composer.lock 会强制锁定版本,哪怕你把 ^2.8 改成 ~2.8.5,composer install 仍按 lock 文件装。
- 想让新约束生效:运行
composer update monolog/monolog(只更新该包) - 想验证谁在阻止升级:用
composer why-not monolog/monolog:2.8.5 - 分支别名(如
dev-main)完全无视 ^/~,它们不是版本,只是指向 commit 的指针
最常被忽略的一点:有些包根本不守 SemVer,^ 和 ~ 在它们身上就是摆设。先看 composer show vendor/pkg 的 latest 版本发布记录,再决定用哪个符号。











