答案是运行composer why-not定位阻塞链,它输出反向依赖链,最后一行是composer.json根声明,第一行是上游封杀者;输出为空则检查require-dev或版本号是否准确。

依赖冲突不是缓存没清或网络不好,是 composer.json 里写的约束互相抵触——删 vendor 或重跑 composer install 只会让同样的失败重演一遍。
用 composer why-not 定位谁在拦路
报错里出现 don't install xxx,别猜,直接查。命令必须带完整版本号,比如:
-
composer why-not guzzlehttp/guzzle:^8.0.0—— 写^8.0或8可能匹配不到元数据 - 输出是从底往上读的反向链:最后一行是你
composer.json的根声明,往上逐行看哪条required by提出了互斥约束,那就是冲突源头 - 输出为空?先检查
require-dev—— 很多冲突来自phpunit/phpunit或orchestra/testbench拖着老版laravel/framework,间接锁死主依赖树
用 composer show --tree 看真实依赖结构
composer.json 是你写的“愿望清单”,composer show --tree 才是 vendor/ 和 composer.lock 里的真实快照。它能暴露你根本没意识到的间接路径:
工作流任务拆解与模型编排技能。使用场景:(1) 收到复杂工作任务需要拆解为可执行步骤,(2) 需要为不同步骤选择最合适的模型,(3) 需要跟踪工作流进度和模型使用情况,(4) 长时间任务卡住需要问题诊断和解决方案。
- 看到
(locked to 5.4.42)—— 这说明该版本已被composer.lock固定,不是当前composer.json声明决定的 - 看到
(replaced)或(provided)—— 得去那个包自己的composer.json里确认它到底替代了什么、是否真能覆盖全部 API - 过滤关键段:
composer show --tree monolog/monolog | grep -A5 -B5 "guzzlehttp/guzzle"
升级或降级单个包必须加 --with-dependencies
不加这个参数,Composer 默认拒绝更新其子依赖,哪怕新版本根本跑不起来:
- 正确写法:
composer update monolog/monolog --with-dependencies—— 只动它和直系依赖 - 错误写法:
composer update "monolog/monolog:^3"—— 引号 +^会让 Composer 自己找“最新”,可能跳过必要联动升级 - 全量
composer update容易触发 SAT 求解器卡死,尤其依赖多的项目;优先用--dry-run验证:composer require laravel/framework:11.0.0 --dry-run
别碰 composer.lock 文件本身
手动编辑 composer.lock 极其危险——它不是配置文件,而是依赖图的哈希快照。改错一个 hash 或 version 字段,下次 install 就会失败:
- Git 合并时出现冲突标记?删掉整个
composer.lock文件(保留冲突标记的那几行不算数),再运行composer update --lock让 Composer 重新生成一致的锁定状态 - 想保留某一分支的依赖结构?就先
git checkout那个分支,再跑composer update vendor/package重装另一分支引入的包 - 永远不要在合并后直接
composer install——lock文件已损坏,install必然失败
最常被忽略的是 require-dev 全程参与解析——哪怕你只执行 composer install --no-dev,dev 包的约束也会影响主依赖树的构建。一个 orchestra/testbench 就足以把整个项目卡死在 Laravel v9。

















