composer update –root-reqs 仅更新 composer.json 中直接声明的顶层依赖,不递归更新子依赖,只调整为满足新根依赖所必需的最小版本集合。

composer update –root-reqs 为什么只改 composer.json 顶层依赖
它本质是「跳过递归更新」:Composer 默认会把整个依赖树(包括子依赖)全拉一遍最新兼容版本,而 --root-reqs 强制只检查 composer.json 里你亲手写的那些包(即 require 和 require-dev 下的直接依赖),不碰它们的子依赖版本。
常见错误现象:composer update --root-reqs 执行后,vendor/ 里某个间接依赖的版本没变,但你期望它跟着主包一起升——这说明你误以为它能“智能联动”,其实它连看都不看子依赖的 composer.json。
- 适用场景:你想升级
laravel/framework到 v11,但不想让symfony/http-Foundation这类底层包被意外升到不兼容的大版本 - 参数差异:
--root-reqs不接受包名列表;想只更某几个根依赖,得写成composer update vendor/package-a vendor/package-b - 性能影响:快很多,尤其在大型项目中,省去了依赖图解析和冲突检测的开销
对比 composer update 默认行为:哪些东西会被动改变
默认 composer update 不仅更新根依赖,还会根据新版本的 composer.json 中的 require 字段,重新计算并拉取所有子依赖的最新满足版本——哪怕你根本没动过它们的版本约束。
典型坑点:monolog/monolog 是 laravel/framework 的子依赖,你只改了 Laravel 版本,结果 Monolog 从 2.x 升到了 3.x,导致日志处理器接口报错 Call to undefined method MonologLogger::pushHandler()。
- 兼容性风险:子依赖升级可能引入 BC break,尤其当主包未锁死子依赖版本时
- lock 文件变化:默认命令会重写
composer.lock中全部依赖条目;--root-reqs只改根依赖及其直接满足的子依赖(仅限为满足新根依赖而必须调整的那部分) - 不是“不更新子依赖”,而是“只更新必要最小集”——比如新 Laravel 要求
symfony/console ^7.0,那这个就会动;但psr/log如果旧版本仍满足,就原地不动
composer why 和 composer depends 配合查根依赖影响范围
光靠 --root-reqs 不知道改一个包会牵连谁,得用诊断命令反查依赖链。比如你准备升级 guzzlehttp/guzzle,先确认它是不是真被你直接 require:
composer depends guzzlehttp/guzzle
输出会列出所有直接或间接依赖它的包;再用:
composer why guzzlehttp/guzzle
看它是被哪个根依赖带进来的。如果返回空,说明它只是子依赖,那你用 --root-reqs 升级其他包时,它大概率不会动。
- 注意:
composer why默认只显示最短路径,加-a才列全路径 - 真实场景中,有些包被多个根依赖共同引用(比如
symfony/polyfill-php81),此时--root-reqs升级其中任一主包,都可能触发 polyfill 更新 - 别信
composer show的版本号——它显示的是vendor/当前装的版本,不是composer.lock记录的解析结果
什么时候不该用 --root-reqs
它不是银弹。当你明确需要同步刷新整棵树(比如上线前做一次干净依赖对齐、排查缓存导致的版本错乱),或者你在迁移到 PHP 8.2 后要确保所有 polyfill 和类型声明支持到位,这时绕过子依赖就是自找麻烦。
- CI 环境慎用:某些构建镜像缓存了旧
vendor/,--root-reqs可能漏掉需更新的子依赖,导致本地正常、线上报错 - 团队协作时,如果别人没跑完整
composer update,composer.lock差异会变大,merge 冲突概率上升 - 最隐蔽的坑:某些包的
replace或conflict规则只在完整依赖解析时生效,--root-reqs可能跳过冲突检测,留下隐患
真正难的不是命令怎么敲,而是判断「这次变更到底要不要传导下去」——得看 changelog、看测试覆盖率、看下游是否已适配,而不是依赖一个 flag 自动决策。