答案是composer why-not是定位阻塞链的唯一可靠命令,它逐层展开require、require-dev及间接依赖中的互斥约束,输出首行即最上游封杀者;若为空则可能版本号错误或minimum-stability限制所致。

composer why-not 是唯一能定位阻塞链的命令
报错里出现 Conclusion: don't install laravel/framework v10.32.0,这不是原因,是结果。真正要查的是“谁在阻止这个版本被选中”。composer why-not 会沿着 require、require-dev 和所有间接依赖,逐层展开阻断路径——输出从底往上读,最后一行是你 composer.json 的根声明,往上第一行就是最上游的硬性封杀者。
必须带完整版本号,例如:composer why-not guzzlehttp/guzzle:^8.0.0。写 ^8.0 或 8 可能匹配不到元数据,导致输出为空。
输出为空?别急着删 lock 文件。先检查 require-dev ——很多本地调试冲突来自 phpunit/phpunit 或 orchestra/testbench 拖着老版 sebastian/exporter,间接锁死 symfony/console 或 laravel/framework。
composer show --tree 才是你依赖的真实快照
composer.json 是你写的“愿望清单”,composer show --tree 才是 vendor/ 和 composer.lock 里实际安装的结构。它能暴露你根本没意识到的间接路径。
关键信息藏在括号里:(locked to 5.4.42) 表示该版本已被 composer.lock 固定;(replaced) 或 (provided) 则说明有包在 replace 字段里声明替代了它——得去那个包自己的 composer.json 确认是否真能覆盖。
过滤查看更高效:
工作流任务拆解与模型编排技能。使用场景:(1) 收到复杂工作任务需要拆解为可执行步骤,(2) 需要为不同步骤选择最合适的模型,(3) 需要跟踪工作流进度和模型使用情况,(4) 长时间任务卡住需要问题诊断和解决方案。
composer show --tree monolog/monolog | grep -A5 -B5 "guzzlehttp/guzzle"composer show --tree | grep "symfony/console"
本地包调试时 require-dev 是隐藏雷区
从 Composer 1.7 开始,require-dev 全程参与依赖解析——哪怕你只执行 composer install --no-dev,它的约束也会影响主依赖树的构建。
典型陷阱:主项目 require laravel/framework: ^10.0,而 orchestra/testbench: ^7.0(在 require-dev 中)强制拉入 laravel/framework: ^9.0,整个依赖树就卡死在 v9.x。
排查建议:
- 临时注释掉
require-dev区块,再跑composer update --dry-run看是否还冲突 - 用
composer depends -r vendor/package查看谁在递归拖后腿,注意带requires的行才是真实约束来源 - 别信
conflict字段——它不参与 solver 计算,只是作者写的提示,不会阻止安装成功
更新单个本地包必须加 --with-dependencies
本地开发中常想只升级某个调试中的包,但直接运行 composer update monolog/monolog 是错的。不加 --with-dependencies,Composer 默认拒绝更新其子依赖,哪怕新版本根本跑不起来。
正确做法:
-
composer update monolog/monolog --with-dependencies—— 只动它和直系依赖 -
composer require vendor/package:dev-main --stability=dev --dry-run—— 先验证能否解出路径,不改任何文件 - 避免全量
composer update,尤其在依赖多的项目里,容易触发 SAT 求解器卡死
手动编辑 composer.lock 极其危险——它不是配置文件,而是依赖图的哈希快照。改错一个 hash 或 version 字段,下次 install 就会失败。Git 合并冲突后,应删掉冲突标记后的整个 lock 文件,再跑 composer update --lock 重生成。










