composer why-not是检测依赖死锁的起点,不是备选方案;它不模拟安装、不修改文件,仅从目标包反向遍历composer.json和composer.lock中的require、conflict及platform约束,精准输出首个不可绕过的硬冲突点,如conflicts行即为真死锁。

先别急着删 composer.lock 或者盲目改版本约束——遇到 root requirements could not be resolved 这种报错时,真正的第一步是跑一下 composer why-not。这玩意儿是唯一一个能立刻告诉你“到底谁在拦路”的只读命令。它不模拟安装、不修改任何文件,只是从目标包出发,反向遍历当前 composer.json 和 composer.lock 里所有的 require、conflict 以及 platform 约束,然后输出第一个根本绕不过去的硬冲突点。
不过有几个常见坑得注意:
- 如果跑
composer why-not lara vel/framework:11.0,结果报了个[InvalidArgumentException] Package not found——八成是版本号没补零,必须写成lara vel/framework:11.0.0,Composer 对版本号格式很较真。 - 输出为空不代表万事大吉,它反而说明目标包没有被任何现有的依赖引用。这时候得去检查拼写、PHP 版本、
minimum-stability设置,或者私有仓库能不能访问得到。 - 输出里要是出现了
conflicts行,比如conflicts: {"lara vel/framework": ">=11"},这就是真的死锁了,Composer 会直接放弃求解。 - 还有个隐蔽的点:如果目标包是被
require-dev里的某个包间接依赖的,那必须加上--with-all-dependencies参数才能穿透检查。
用 composer prohibits 主动扫描阻断源头
假设你打算升级主框架到 Lara vel 11,但还没写进 composer.json 里,这时候 composer why-not 会直接返回空——因为它只检查已经注册的依赖。换个思路,该用 composer prohibits。这个命令不等你“想装什么”,而是主动扫描整个依赖图,把所有明确写着排斥目标版本的包都给列出来。
实操建议:
- 升级 Lara vel 之前先跑
composer prohibits "lara vel/framework:11.0.0",提前看看spatie/lara vel-backup或者orchestra/testbench是不是还在锁死旧版。 - 输出里带有
(by your requirements)的行,那是你自己的composer.json在捣乱,不是第三方包的问题。 - 如果多个包都禁止同一版本,优先处理
require-dev里的工具链包——这些包在测试兼容性上往往滞后于主框架。
依赖树分析:用 show -t --no-dev 定位循环引用
当 composer install 卡住不动、CPU 持续飙升、内存暴涨,大概率不是网络慢,也不是包不存在,而是依赖图里出现了 a → b → a 这种强循环。SAT 求解器在无限回溯,怎么也解不开。
关键操作:
- 运行
composer show -t --no-dev --ignore-platform-reqs,这样才能逼近真实安装路径。默认行为会包含require-dev并跳过平台检查,反而会掩盖真实的冲突。 - 人工扫描输出中反复出现的包名组合。比如:
vendor/a/package-a→vendor/b/package-b→ 又回到vendor/a/package-a。 - 如果某个包在树中展开超过 3 次,且路径不同,也值得怀疑。Windows cmd 可能会截断长输出,建议用 PowerShell 或者加
| more。
封装可配置检测器:核心是命令组合与参数隔离
所谓的“可配置的 PHP 分布式死锁检测器”,本质上不是写什么新逻辑,而是把 why-not、prohibits、show -t 这三类命令封装成一个统一入口,同时做好参数隔离。比如可以做一个 CLI 工具,接受 --target、--mode=why-not|prohibits|tree、--include-dev 等开关,内部根据模式调用对应命令并过滤输出。
有几个注意事项:
- 别试图在 PHP 层面去解析 Composer 的输出——直接
exec()调用原生命令更可靠,省得自己重实现一遍约束解析。 --dry-run -v在卡住的时候非常关键。它会让日志吐出 SAT 求解器的真实决策痕迹,这不是普通的“日志”,而是求解器的回溯路径。- 所有命令都依赖 Composer ≥ 2.2。低于这个版本得先跑
composer self-update --2,否则会提示Command "why-not" is not defined。 - 真正难处理的从来不是命令怎么写,而是怎么判断“这个空输出到底是没冲突,还是环境不满足”——PHP 版本、扩展、私有源认证状态,
why-not都不负责校验。