必须用"=版本号"语法锁定包版本,如"phpunit/phpunit": "=9.6.13";composer.lock未提交或被忽略会导致install退化为update;CI中需加--no-updates参数并校验lock文件。

直接锁死版本,别让 composer update 悄悄升级——靠 composer.lock 和约束写法双重保障。
为什么 composer install 有时还是升了版本?
常见错误现象:明明没跑 composer update,但部署后行为异常,查发现某个包版本变了。根本原因常是:composer.json 里用了模糊约束(比如 "monolog/monolog": "^2.0"),而本地开发机上执行过 update,生成的 composer.lock 又没提交或被忽略。
-
composer install严格按composer.lock安装,但前提是 lock 文件存在且未被篡改 - 如果 lock 文件缺失、被删、或 git 忽略了它,
install会退化为update行为 - 团队协作中,有人提交了新 lock,有人没拉取,就会导致环境不一致
怎么写死一个包的精确版本?
不是靠注释或文档约定,而是用 composer.json 的约束语法强制锁定。最稳妥的是「等号 + 版本号」,它无视所有语义化版本规则,只认这个 exact 版本。
- 写成
"phpunit/phpunit": "=9.6.13"—— 这个包永远只能是 9.6.13,连 9.6.14 都不会装 - 避免用
~或^,它们在 minor/patch 级别都允许变动,不是“锁定” - 如果依赖其他包间接引入该包,需在根
composer.json中显式声明并加=,否则约束不生效
示例:
"require": {
"guzzlehttp/guzzle": "=7.8.1",
"symfony/console": "=6.4.7"
}
composer update 时只更新某几个包,其余全锁住
场景很实际:你只想升级 laravel/framework 到新 patch,但必须确保 doctrine/dbal 死死卡在 3.7.2——因为已知 3.8.x 有兼容问题。
- 运行
composer update laravel/framework,不带其他包名,其余所有包都会保持composer.lock记录的版本 - 如果想同时升两个,写成
composer update laravel/framework doctrine/dbal - 加
--with-all-dependencies会破坏锁定逻辑,慎用;它会让依赖树里相关包也跟着“松动” - 执行前确认
composer.lock已提交,否则 update 后的 lock 可能包含意外变更
CI/CD 里怎么防住意外升级?
线上构建最怕自动升级,尤其当 composer install 命令没配对参数时。
- CI 脚本里必须加
--no-interaction --no-progress --prefer-dist,但关键还要加--ignore-platform-reqs?不,那是另一类问题;真正防升级的是--no-updates—— 它强制install不尝试同步 lock 文件 - Docker 构建中,确保
composer.lock在COPY阶段早于composer install,且路径正确 - Git 预提交钩子可以检查
composer.json里是否有非=的约束,但更现实的是:把composer.lock加进 CI 流水线校验步骤,diff 它是否被无意修改
复杂点在于:lock 文件本身是二进制安全的 JSON,但不同 Composer 版本生成的字段顺序、空格、注释处理可能不同,diff 容易误报——所以比对前最好统一用 composer self-update --2 固定版本。










