composer why-not vendor/package:^X.Y可直接定位强制降级源头——输出为倒序链,最后一行是composer.json根声明,往上每行“(required by)”揭示压低版本的依赖路径;composer show --tree则暴露实际安装快照,揭示locked、replaced等隐式约束。

composer why-not 能直接定位谁在强制降级
报错里出现 don’t install vendor/package:^X.Y,就立刻执行 composer why-not vendor/package:^X.Y。输出是倒序链:最后一行是你 composer.json 里的根声明(比如 "laravel/framework": "^10.0"),往上每行末尾的 (required by) 就是某条依赖路径在压低版本。常见隐形降级源包括:phpunit/phpunit 锁死 sebastian/exporter,间接拖住 symfony/console;laravel/sanctum 要求 illuminate/support ^10.0,而你项目还卡在 Laravel 9。
composer show --tree 揭露真实依赖快照
composer.json 是你写的“愿望清单”,composer show --tree 才是 vendor/ 和 composer.lock 里实际安装的快照。它能暴露你根本没意识到的间接路径,比如某个包标着 (locked to 5.4.42) 或 (replaced)。查某个包被谁引入:composer show --tree | grep "symfony/console";过滤关键段:composer show --tree monolog/monolog | grep -A5 -B5 "guzzlehttp/guzzle";看到 (provided) 或 (replaced)?得去那个包自己的 composer.json 查 replace 字段,确认是否真能等价替代。
升级或降级单个包必须加 --with-dependencies
composer update vendor/package 默认拒绝更新其子依赖,哪怕新版本根本跑不起来——这是最常踩的坑。正确做法是:composer update monolog/monolog --with-dependencies,只动它和直系依赖;降主版本(如从 guzzlehttp/guzzle:^8.0 切回 7.4.5)必须先在 composer.json 里写死 "guzzlehttp/guzzle": "7.4.5",再跑 composer update guzzlehttp/guzzle --with-dependencies。错误写法:composer update "monolog/monolog:^3" —— 引号 + ^ 会让 Composer 自己找“最新兼容版”,不是你要的 3.0.0。执行后立刻 git diff composer.lock,确认只有目标包及其直系依赖被改。
别删 vendor 或 composer.lock,那是重演失败过程
依赖冲突不是网络或缓存问题,是 composer.json 里写的约束互相打架。删 vendor/ 或 composer.lock 只会触发完全相同的解析失败流程。真正要做的,是定位哪条依赖链在封杀目标版本。如果 Git 合并导致 composer.lock 冲突,干净合并 composer.json 的 require 和 require-dev 块,然后删掉 composer.lock 和 vendor/,再跑 composer update —— 它会重新解析整个依赖树。若只想最小化变动,用 composer update --minimal-changes(Composer 2.2+)。


















