composer install只读composer.lock是因为其设计目标是“还原”而非“决策”,它严格按lock中记录的精确版本、SHA256校验值、dist URL和依赖树快照安装,完全忽略composer.json中的版本约束与PHP兼容性检查。

composer install 为什么只读 composer.lock,不看 composer.json 的版本约束
因为 composer install 的设计目标就是“还原”,不是“决策”。它只信任 composer.lock 里写的每个包的 exact version、dist archive 的 sha256 校验值、安装路径和完整依赖树快照。哪怕 composer.json 里写着 "php": "^8.2",只要锁文件里记录的是基于 PHP 8.1 构建的依赖,install 就照装不误——它不校验兼容性,也不重新解析。
常见错误现象:
- 本地 PHP 升级到 8.3,但
composer.lock还是旧的,install成功,运行时报Fatal error: Uncaught Error: Call to undefined function mb_str_split() -
composer.json改了"guzzlehttp/guzzle": "7.9.0",但没跑composer update guzzlehttp/guzzle,直接install—— 结果还是装的 7.8.1
composer update 才真正触发依赖解析,且必须显式调用
composer update 是唯一会重新走依赖解析流程的命令:它读取 composer.json 的所有约束(包括 ^、~、*),结合当前平台环境(PHP 版本、已启用扩展)、镜像源响应、安全 advisory 数据库,生成一棵新的依赖树,并覆盖写入 composer.lock。
关键细节:
- 不带参数的
composer update会重算整棵树,可能连带升级symfony/polyfill或降级psr/cache(因新版本要求更高 PHP) - 推荐用
composer update monolog/monolog --with-all-dependencies控制影响范围 -
composer require foo/bar实际上是先改composer.json,再隐式执行composer update foo/bar,也会改锁文件
锁文件缺失时,composer install 会退化为不受控的 update
这不是“自动 fallback”,而是明确的行为定义:composer install 在找不到 composer.lock 时,会立即执行一次等价于 composer update 的操作来生成它,然后再继续安装。整个过程不可审计、不可复现。
典型后果:
- CI 流水线里漏提交
composer.lock,导致每次构建都拉最新 patch 版本,某天突然因ramsey/uuid4.7.5 的 autoload bug 导致Class not found - 同事 A 本地删了 lock 文件,
install后 vendor 多出psr/http-message;B 没删,vendor 里没有——两人composer.json完全一致 - 安全扫描工具(如
composer audit)失去基线,无法比对 CVE 是否被引入
lock 文件不是配置,不能手动编辑或合并冲突
composer.lock 是整个依赖图的完整序列化快照,包含 300+ 行嵌套 JSON,字段间强耦合。手动删掉某段 “conflict” 或保留 “ours” 的 version、“theirs” 的 hash,大概率导致 install 中途失败,或装出不一致的 vendor。
正确做法:
- 遇到 Git 冲突,直接
git checkout --ours composer.lock或--theirs任选其一恢复 - 删掉
vendor/目录 - 运行
composer install—— 它会基于当前composer.json和刚恢复的 lock 重新校验并重建一致状态 - 如果报
Your lock file does not contain a compatible set of packages,说明composer.json已被他人修改,先git pull,再重试
最容易被忽略的一点:锁文件里每个包的 dist.sha256 不只是防篡改,还决定了是否命中 CDN 缓存。手动改 version 但不更新 hash,install 会因校验失败中止,而不是静默跳过。


















