composer.lock 是部署契约,声明所有环境必须安装完全相同的依赖;它记录精确版本、校验值和完整依赖树,composer install 仅还原lock内容,忽略composer.json的版本约束。

composer.lock 是部署契约,不是缓存或日志
它声明“所有环境必须装一模一样的依赖”,不是记录你装过什么的流水账。一旦提交到 Git,CI、测试机、生产服务器就都得按这个快照来——删了它再跑 composer install,等于主动放弃控制权,退化成一次不受控的 composer update。
常见错误现象:
- CI 构建失败报
Class not found,但本地能跑 → 很可能有人删了composer.lock或没提交新版本 - 团队两人执行
composer install后vendor/内容不一致 → 其中一人本地锁文件被删或覆盖 - 某次部署后突然出现
ArgumentCountError→ 某个子依赖(如symfony/http-foundation)从v6.4.10升到了v6.4.11,而后者改了内部方法签名
composer install 为什么只认 lock 文件,完全忽略 composer.json 的 ^ 和 ~
composer install 的设计目标就是“还原”,不是“解析”。它只读 composer.lock 里写的 exact version、dist archive 的 sha256 校验值、安装路径、完整依赖树,对 composer.json 中的 ^8.0、~3.1、* 等一概无视。
关键点:
- 缺失
composer.lock时,composer install会先隐式执行一次等价于composer update的操作来生成它,再继续安装——但这属于 fallback,不是本意 -
composer.lock里还存着platform字段(如"php": "8.2.15"),不同 PHP 版本的机器上,即使 lock 相同,install也可能失败 - 它不检查 PHP 版本兼容性——那是
composer update阶段的事;install只管“还原”,不管“是否合理”
什么时候该用 composer update,而不是删 lock 后重装
composer update 是唯一受控升级依赖的入口。它会重新解析 composer.json 中所有约束,生成新依赖树,并覆盖写入 composer.lock。删 lock 文件再装,等于跳过所有版本决策过程。
实操建议:
- CI/CD 流水线中永远用
composer install,且确保composer.lock已存在;禁止在部署脚本里调用update - 误删
composer.lock后,应从 Git 恢复(如git checkout HEAD -- composer.lock),而非重建 - 执行
composer update前务必确认:composer.json已git add并提交;更新后必须立刻git add composer.lock并提交 - 安全扫描工具(如
phpstan-security)依赖composer.lock比对历史基线,没它就无法判断某次更新是否引入已知漏洞
应用项目必须提交 composer.lock,库项目则不能
这是最容易混淆的一点:应用项目(Laravel、Symfony、纯 CLI 工具等)必须把 composer.lock 提交进 Git;而库项目(比如你写的 SDK 包)不应提交,因为它的依赖由下游应用决定。
关键细节:
-
composer.lock不只是主依赖,还包括每一层嵌套依赖的commit hash和dist校验值 - Git 提交记录里若出现
composer.lock被删又重建,说明某次发布已失去可追溯性,后续出问题很难定位是否由依赖变更引起 -
composer install --locked是上线前最后一道保险:它校验lock里每个版本是否仍满足composer.json的约束,不满足直接报错退出











