composer why-not是诊断依赖冲突的第一响应命令,它不查已安装依赖链,而是逆向扫描阻止目标版本安装的所有约束,精准定位“谁在拦着你升版本”,输出冲突路径且不依赖锁文件是否最新。

直接看 composer why-not,不是 composer why,也不是翻 composer.json 或 composer.lock 手动比对。
为什么 composer why-not 是第一响应命令
当你执行 composer require vendor/package:2.0 或 composer update vendor/package 失败,并看到类似 Conclusion: package-a 1.5.0 requires package-b ^1.0 -> satisfiable by package-b[1.0.0, 1.1.0] 这类嵌套冲突提示时,composer why-not 会直接告诉你“谁在拦着你升这个版本”。它不查已安装的依赖链,而是模拟升级动作,逆向扫描所有阻止该版本被选中的约束条件。
- 它输出的是“冲突路径”,每行代表一个阻断点,比如:
laravel/framework v10.48.12 requires monolog/monolog ^2.9→monolog/monolog 2.9.0 requires php ^8.0→ 但你本地是 PHP 7.4 - 不依赖
composer.lock是否最新——哪怕锁文件过期,它也能基于当前composer.json和远程包元数据实时推演 - 不显示“谁装了它”,只显示“谁不让它升”,语义精准匹配你此刻的卡点
composer why-not 的典型使用场景和参数组合
它常被忽略,是因为名字不像 why 那样直白,但它才是解决“明明有新版却死活升不了”问题的默认入口。
- 查单个包升级失败原因:
composer why-not monolog/monolog:3.0 - 查当前项目是否能支持某 PHP 版本:
composer why-not php:8.2 - 加
-v看详细约束来源:composer why-not -v guzzlehttp/guzzle:^7.5,会列出每个上游包的require字段原文 - 加
--no-dev排除开发依赖干扰(比如phpunit强制绑定了旧版sebastian/exporter)
为什么 composer why 和 composer depends 在这里帮不上忙
composer why 只回答“谁把我装进来”,composer depends 回答“谁可能用到我”,两者都基于已安装状态。而版本冲突发生在“还没装成”的决策阶段——它们查的是结果,why-not 查的是决策过程本身。
-
composer why monolog/monolog可能只返回laravel/framework,但不会告诉你 laravel/framework 的某个子依赖(如symfony/console)悄悄锁死了psr/log的版本,进而反向压制了monolog的升级空间 -
composer depends psr/log会列出一堆包名,但无法说明哪一条路径实际触发了冲突;它查的是声明关系,不是解析时的真实约束传播链 - 真正关键的往往是
conflict字段或replace声明,这些只在why-not的推演中被激活并暴露
容易被忽略的细节:冲突未必来自 require,也可能是 platform 或 config
很多团队卡在“明明没写任何限制,为什么升不了”,其实是隐式约束在起作用。
-
composer.json里的"config": {"platform": {"php": "7.4.33"}}会覆盖真实 PHP 版本,导致why-not php:8.0显示“因为 platform.php=7.4.33” - 某些包在
composer.json中声明"conflict": {"ext-gd": ",而你系统 GD 扩展是 1.9 ——why-not会明确指出这个扩展冲突,why完全看不到 - 私有源或
path类型仓库若未配置allow-plugins,why-not会报错提示插件禁用,而其他命令可能静默跳过











