要真正锁定不可变版本1.2.3,须写"vendor/package": "1.2.3"且设"minimum-stability": "stable"、"prefer-stable": true;更稳妥用"1.2.3@stable"。^1.2.3允许升至1.x.x(如1.9.9),~1.2.3仅限1.2.x(如1.2.9)。

Composer 不支持“精确版本”这种说法——1.2.3 看似精确,但默认会匹配 1.2.3-patch1、1.2.3-beta1 等带后缀的版本,除非你显式禁用稳定性约束。
如何真正锁定一个不可变的版本(比如 1.2.3)
想让 Composer 只安装 1.2.3 这个确切的 tag,且拒绝任何 -dev、-beta、-rc 或 -patch 后缀,必须同时满足两个条件:
- 使用
^1.2.3或~1.2.3是不够的——它们仍会升到1.2.4或1.3.0(取决于规则) - 正确写法是:直接写死
"vendor/package": "1.2.3"(无符号),并确保"minimum-stability"为stable,且"prefer-stable": true(否则 Composer 可能降级选 unstable 版本) - 更保险的做法是加
@stable后缀:"vendor/package": "1.2.3@stable",这会强制忽略所有非 stable 后缀变体
^ 和 ~ 的实际行为差异在哪
两者都做“向后兼容升级”,但边界不同,容易误判兼容范围:
-
^1.2.3≡ 允许升级到1.x.x,但不跨2.0.0;等价于>=1.2.3 -
~1.2.3≡ 允许升级到1.2.x,但不跨1.3.0;等价于>=1.2.3 - 对主版本为
0.x的包(如0.8.1),^0.8.1只允许0.8.x,行为退化为~0.8.1;这是很多人踩坑的点 - 如果写成
^0.0.1,它只接受0.0.1自身——因为0.0.x范围内几乎无合法升级空间
为什么 1.2.* 有时装不到预期版本
这个通配符看似直观,但它依赖 Packagist 上是否真有对应 tag,且受 stability 影响极大:
-
1.2.*实际展开为>=1.2.0 ,和 <code>~1.2.0等价,不是“任意 1.2 开头的字符串” - 如果包只有
1.2.0和1.2.5两个 stable tag,而你本地 cache 过期,Composer 可能因元数据未刷新而“看不见”1.2.5,最终锁住旧版 - 若该包在
1.2.3发过1.2.3-beta1,而你没设"minimum-stability": "stable",Composer 可能选中 beta 版——1.2.*不排除 unstable - 解决方法:运行
composer clear-cache+composer update vendor/package --with-dependencies,避免隐式继承旧 lock
生产环境应避免的写法
这些写法看似灵活,实则埋下部署不一致、CI 失败、甚至安全更新遗漏的隐患:
-
"*":完全放开,下次 install 可能拉到 breaking change 版本 -
"dev-main"或"dev-develop":分支名不是版本,无法复现,且可能被 force-push 覆盖 -
"^1.2"(缺补丁号):等价于^1.2.0,但若作者跳过1.2.0直接发1.2.1,Composer 会从1.2.1开始算 —— 行为不如写全三位明确 - 混合使用:
"^1.2.3 || ^2.0.0"在 require 中非法,Composer 不支持 OR 逻辑;需靠replace或分拆到不同环境配置
最常被忽略的是:即使写了 "1.2.3",如果 composer.lock 里记录的是 1.2.3+commit-hash(来自 dev 分支安装残留),后续 composer install 仍会还原那个 commit,而非 tag。检查 composer show vendor/package 输出中的 source 字段,比看 require 行更可靠。











