真正锁死依赖需在composer.json中写纯数字三位版本号如"monolog/monolog": "2.9.1",执行composer update monolog/monolog更新lock文件,提交composer.lock至Git,并在部署时仅运行composer install;缺一不可。

composer.json里写"2.9.1"才算真正锁死
写"monolog/monolog": "2.9.1"是唯一能阻止自动升级的写法。任何带符号的写法——包括"^2.9"、"~2.9.0"、"2.9.*"、甚至"2.9"——都会在composer update时漂移。原因很简单:^和~是范围约束,不是锁定指令;而"2.9"在不同 Composer 版本中解析不一致(有的当^2.9.0,有的报错),根本不可靠。
改完必须立刻运行:composer update monolog/monolog。否则composer.lock不会更新,新机器上composer install仍可能装错版本。
- 验证是否生效:运行
composer show monolog/monolog,输出必须是2.9.1 - 再跑一次
composer update monolog/monolog,若提示Nothing to install or update,才算锁成功 - 更保险可加
@stable后缀:"monolog/monolog": "2.9.1@stable",强制排除-beta、-rc等非 stable 变体
为什么composer.lock必须提交到 Git
composer.lock不是缓存,是整个依赖树的哈希快照:它记录每个包的确切版本、源码 commit hash、dist URL、ZIP 校验和、嵌套依赖路径。没有它,composer install就退化为按composer.json重新解析依赖——哪怕你写了"2.9.1",只要lock文件缺失或被.gitignore,CI 构建、新同事拉代码、Docker 构建时都可能装上2.9.2(尤其镜像源缓存了新版本)。
删掉composer.lock后执行composer install,等价于执行了一次composer update,所有包都会漂移。
- CI/CD 流水线必须用
composer install --no-dev --no-interaction,绝不能用update - 上线前可用
composer install --locked校验:它会检查composer.lock中每个版本是否仍满足composer.json的约束,不满足直接报错退出 - 团队协作中忽略或手动编辑
composer.lock,等于主动放弃依赖一致性
^和~的行为差异远比想象中大
^1.2.3等价于>=1.2.3 ,允许升到1.9.9;而~1.2.3等价于>=1.2.3 ,只允许升到1.2.999。很多人以为~更宽松,其实它更保守。
对主版本为0.x的包(如0.8.1),^0.8.1行为退化为~0.8.1,只允许0.8.x;而^0.0.1只接受0.0.1自身——连0.0.2都被排除。
-
^适合生态稳定、严格守 SemVer 的包(如laravel/framework) -
~适合私有包或发布节奏不规律的项目,想把 MINOR 也锁死 - 写
~2.15不推荐——它等价于~2.15.0,但省略补丁号易引发歧义;应明确写~2.15.0
分支依赖怎么才算真正锁定
写"monolog/monolog": "dev-main"只是拉最新 HEAD,每次composer update都可能变——这不是锁定,是漂移。
真正锁定某次提交,必须用dev-main#abc1234格式(abc1234是实际 commit hash)。分支名含斜杠(如feature/login-flow)需 URL 编码为feature%2Flogin-flow,否则解析失败。
用分支代替 tag 是高风险操作,仅建议用于临时调试或等待正式发布。生产环境必须用 tag 或精确 commit 锁定。
- 不要写
"dev-main#v2.9.1"——v2.9.1不是 commit hash,Composer 会忽略 - commit hash 必须是完整 40 位或至少前 7 位(如
abc1234),且确保该 commit 在远程仓库存在 - 锁定分支后仍需运行
composer update package-name并提交更新后的composer.lock











