composer install根本不看composer.json里的版本号,因为它专为“确定性复现”设计,只严格按composer.lock中记录的精确版本、哈希值和依赖树还原安装;修改composer.json后必须运行composer update才能更新lock文件。

composer install 为什么根本不看 composer.json 里的版本号
它只读 composer.lock,连 ^7.0 或 ~2.5 这类写法都完全无视。你改了根 composer.json 的 require 字段,只要不跑 composer update,install 就不会感知——它只当 lock 是唯一权威。
常见错误现象:
- 本地改完
composer.json新增一个包,直接composer install却没装上 → 因为lock里没这条记录,它拒绝“擅自添加” - CI 构建报
Class not found,但本地正常 → 很可能 CI 拉到的composer.lock是旧版,或压根没提交到 Git
composer update 才真正触发依赖图解析
update 会递归读取所有已知包的 composer.json(包括你 require 的、它们 require 的、再下一层 require 的),构建整张依赖图,再交给 SAT 求解器找满足全部约束的唯一解。
这个过程受三重影响:
-
root composer.json中的require约束(如"guzzlehttp/guzzle": "^7.8") - 每个子包自身
composer.json声明的require和php版本要求(如"psr/http-client": "^1.0") - 当前 Composer 二进制版本(比如 2.4.3 vs 2.5.0 可能在相同输入下选出不同
symfony/console版本)
为什么删了 composer.lock 就等于强制 update
缺失或损坏 composer.lock 时,composer install 会退化为 composer update 行为:开始解析约束、调用 SAT 求解器、重新生成 lock 文件。这不是“补锁”,而是从头算一遍依赖树。
所以这些操作等价:
rm composer.lock && composer install-
composer update --locked(仅重生成 lock,不装包) -
composer update(默认行为)
团队必须提交 composer.lock,且禁止用文本编辑器手动改它——哪怕只改一个逗号,都会破坏哈希校验,导致后续 install 拒绝执行。
嵌套依赖版本到底从哪来
composer.lock 里存的是完整快照:每个包的 name、version、source、dist、require 列表,甚至包括它的 autoload 配置。但它的 version 字段不是“最终安装版”,而是“当时 resolve 出来的结果”。
也就是说,monolog/monolog 的子依赖 psr/log 版本,由两层共同决定:
- 根
composer.json是否对psr/log有直接约束(通常没有) -
monolog/monolog自身composer.json中写的"psr/log": "^1.0 || ^2.0",以及当前 Composer 解析器选出来的满足该约束的精确版本
这就是为什么同一份 composer.json,在不同 Composer 版本或不同 PHP 平台配置下,install 出来的 vendor/ 目录结构可能不一致——lock 文件才是唯一可信来源,不是代码也不是文档。











