composer why-not 输出是拒绝理由链,须倒序读:最后一行是根需求(如 your-project dev-main requires guzzlehttp/guzzle:^8.0),往上每行末尾 (required by ...) 为追溯起点;必须带完整版本标识(如 monolog/monolog:^2.9),否则提示 package not found。

composer why-not 输出必须从最后一行倒着读
它不是因果链,而是拒绝理由链。最后一行才是你 composer.json 里写的根需求,比如 your-project dev-main requires guzzlehttp/guzzle:^8.0;往上每行末尾的 (required by ...) 才是向上追溯的起点。
常见误判:看到某行写 conflict with symfony/console (>=6.0),就去改 symfony/console——其实那是它自己声明的 conflict 规则,Composer 只是执行。真正卡死的,往往是上一级包对它的版本要求不一致。
- 必须带完整版本标识,例如
composer why-not monolog/monolog:^2.9;若返回Package not found,说明该版本根本没在 Packagist 当前通道注册 - 如果某行是
laravel/framework v10.32.0 → requires symfony/console ^6.4,另一行是package-x v2.1 → requires symfony/console ^5.4,冲突点就在symfony/console,不是guzzlehttp/guzzle - 输出中出现
phpunit/phpunit不代表你要卸载它——它很可能是被orchestra/testbench拉入,而后者又硬绑了laravel/framework:^9.0,那才是真源头
composer show --tree 揭露真实依赖路径而非“理想状态”
composer show --tree 展示的是 vendor/ 和 composer.lock 的真实快照,不是你 composer.json 里写的“应该怎样”。很多冲突藏在第二、三层依赖里,靠这个命令才能暴露。
重点盯三点:同名包是否在不同路径下被引入多个版本;冲突包是否来自 require-dev(它默认参与解析,哪怕你部署时加了 --no-dev);某行末尾是否带 (locked to 5.4.32)——这意味着这个版本已在 composer.lock 固定,不删 lock 或不用 --with-all-dependencies 就没法松动。
- 树太深时直接
grep过滤:composer show --tree monolog/monolog | grep -A5 -B5 "guzzlehttp/guzzle" -
composer show --tree | grep "symfony/console"能快速看到它被谁引入、路径多深、是否已锁定 - 注意
replace声明(如symfony/polyfill-mbstring替代ext-mbstring),但实际运行时没装扩展,会导致假性通过
composer update --dry-run -v 是唯一能触发完整回溯求解的诊断命令
它完整走一遍 SAT 求解器逻辑,但不改任何文件。关键不是看开头 Resolving dependencies,而是盯住末尾几行反复出现的包名和版本号——那里藏着回溯失败的真实组合。
如果输出中反复出现类似:
围绕关键发现、作用机制、临床相关性及研究局限性展开讨论。适用于撰写或优化任何生物医学论文的“讨论(Discussion)”部分——包括结果解读、与既往文献关联、阐释意外发现、界定研究局限性,以及撰写结论。当用户输入以下任一指令时也会自动触发该功能: - “write my discussion” - “help me discuss my findings” - “how do I compare to prior studies” - “write the limitations par
package-a v1.2.0 requires package-b ^2.0 package-b v2.3.0 requires package-a ^1.0 package-a v1.2.0 requires package-b ^2.0
那就是实锤循环依赖。这不是卡顿,是求解器在死循环里打转。
- 出现
Root package requires X but Y is locked to Z:说明composer.lock里某包被硬锁,而新需求要更高/更低版 - 看到
is not satisfiable by ... because ... depends on ...:这是链式冲突,A 包要 B 2.x,B 2.x 又要求 C 3.x,但你的项目直接 require 了 C 1.x - 大量
downgrading提示比升级更危险——可能触发运行时 break change
composer depends --tree 是定位闭环依赖的唯一可靠方式
它能直接显示依赖方向(箭头表示“被谁依赖”),但有两个硬前提:项目必须已成功运行过 composer install(否则依赖图不完整),且必须带 --tree 参数(否则只显示一级)。
假设你怀疑 myorg/core 被循环引入,运行 composer depends myorg/core --tree。若输出中出现:
myorg/core ← myorg/api ← myorg/core
那就闭环了。常见误判点:输出为空?说明该包根本没进 composer.lock,可能已被早期拦截,先删 vendor/ 和 composer.lock 再重试;只看到 myorg/core ← myorg/api?那是正常单向依赖,--tree 没加全,命令无效。
抽离公共契约是唯一可持续的破环方式。所谓“绕过”“忽略”“强制安装”全是临时补丁,真正解决问题得从结构上切断闭环——把双方共用的东西拎出来,建一个新包,比如 myorg/contracts。










