composer why-not 是唯一能定位首个阻断点的命令,它模拟安装指定版本并输出反向依赖链,最后一行是根声明,往上逐行显示谁因互斥约束卡住,需写全包名和精确版本号。

Composer 不会“自动检测冲突”并提前预警,它只在 install/update 时尝试求解依赖图;失败那一刻才是它真正“检测到”的时刻。想提前发现,得靠人主动触发分析命令。
composer why-not 是唯一能定位首个阻断点的命令
它不是检查“有没有冲突”,而是模拟安装某个版本,然后告诉你哪条路径最先卡住——这才是最接近“自动检测”的实操方式。
- 必须写全包名和精确版本号,比如
composer why-not guzzlehttp/guzzle:^8.0.0,写^8.0可能匹配不到元数据 - 输出是反向链:最后一行是你
composer.json的声明,往上逐行看谁写了required by并提出互斥约束 - 如果输出为空,先检查
require-dev—— 很多冲突来自phpunit/phpunit拖着老版sebastian/exporter,间接锁死symfony/console - 加
--dry-run配合composer require也能提前看到结果,比如composer require laravel/framework:11.0.0 --dry-run
composer show --tree 揭露真实依赖结构,而非你写的“愿望清单”
composer.json 是你写的约束,composer show --tree 才是 vendor/ 和 lock 文件里实际落地的快照。它帮你确认:为什么明明写了 ^3.0,最终却装了 2.9.9?
- 用
composer show --tree monolog/monolog | grep -A5 -B5 "guzzlehttp/guzzle"快速过滤关键路径 - 注意末尾带
(locked to 5.4.42)的行——说明这个版本已在composer.lock固定,删 lock 再 update 才可能松动 - 看到
(provided)或(replaced),得去那个包自己的composer.json确认它是否真能覆盖目标功能
composer depends -r 查谁在强制拖后腿
当你发现某个包被锁在旧版,想快速知道是哪个上游组件在提硬性要求,composer depends -r 就是答案。
- 比如
composer depends -r symfony/console会列出所有直接或间接依赖它的包,并标出各自声明的约束范围 - 重点看带
requires的行;replaces或provides的不用管 - 注意
^4.4和~4.4.0表面相似,但允许的版本范围有细微差别,常是冲突隐藏点
真正的难点不在命令怎么敲,而在于理解 composer.lock 里的版本是“已决议结果”,不是“当前约束”。很多所谓“自动检测失败”,其实是人没去看 (locked to X.Y.Z) 这一行——它比 composer.json 里的写法更有决定权。


















