composer.lock 是保障环境一致性的强制契约,必须提交至 Git;执行 composer install 严格按锁文件安装,而 composer update 会忽略锁文件重新解析依赖,导致不同环境安装结果不一致。

更新所有包时 composer.lock 会被重写,这会破坏环境一致性
执行 composer update(不带参数)会忽略 composer.lock 中记录的精确版本,转而根据 composer.json 的版本约束重新解析依赖树,并生成全新 composer.lock。这意味着:同一份 composer.json 在不同时间、不同机器上运行,可能装出完全不同的包组合。
常见错误现象:
— 本地开发正常,CI 构建失败或线上报 Class not found
— 两个同事 git pull 后运行 composer install,结果装出不同版本的 monolog/monolog
- 根本原因不是 Composer 本身有问题,而是
composer.lock没被提交到 Git,或有人误用update替代install -
composer.lock不是“缓存”,它是生产环境可复现安装的唯一依据 - 只要
composer.lock存在且未被修改,composer install就只会按锁文件安装,不查远程仓库
composer update 和 composer install 的行为差异直接影响部署稳定性
二者调用路径不同,触发逻辑也完全不同:
-
composer install:只读composer.lock→ 安装其中锁定的每个包的精确版本(含 sub-dependency 的 patch 版本) -
composer update:忽略composer.lock→ 重新 resolve 所有依赖 → 可能升级到新 minor 或 patch → 重写composer.lock - CI/CD 流水线中若混用二者,比如先
update再install,等于把“临时实验结果”当正式部署依据
典型场景:上线前想“顺手更新下安全补丁”,直接跑 composer update,结果连带升级了 symfony/http-kernel,触发了一个已知的 BC break —— 因为新版本移除了某个 deprecated 方法,而项目里某处还调用了它。
只更新特定包仍可能引发隐式兼容问题
即使你指定包名(如 composer update guzzlehttp/guzzle),Composer 仍会递归更新其全部子依赖,只要它们满足新版本的约束条件。这不是 bug,是依赖求解器的必然行为。
- 例如
guzzlehttp/guzzle ^7.2升级到^7.8,可能顺带把psr/http-message从1.0.1升到2.0.0—— 而后者是不兼容的 major 版本 - 这种“间接升级”不会出现在
composer.json中,但会写进新生成的composer.lock,且无任何提示 - 建议操作前先运行
composer update --dry-run guzzlehttp/guzzle,看实际会动哪些包
锁定文件不是“可选附件”,而是生产环境的强制契约
composer.lock 文件必须和 composer.json 一起提交到 Git,且每次变更都要经过 review。它的核心作用不是加速安装,而是让 dev、test、prod 三套环境共享同一组依赖快照。
容易被忽略的一点:即使你用 --no-dev 安装生产依赖,composer.lock 里依然包含 dev 包的版本信息 —— 因为 lock 文件记录的是完整解析结果。所以删掉 composer.lock 再 install --no-dev,得到的仍是不可控的结果。


















