答案是运行composer why-not定位阻塞链,输出倒序显示依赖层级,最后一行是根require,往上每行“(required by)”即封杀源头;输出为空则先查require-dev或版本号准确性。

直接改 composer.json 或删 vendor 解不了依赖冲突,因为冲突根源是版本约束之间没有交集——Composer 已穷举所有组合,确认无解才报错。
用 composer why-not 定位谁在封杀目标版本
报错里出现 don't install vendor/package:version 时,别猜,立刻查阻断链:
- 必须带完整版本号,例如
composer why-not guzzlehttp/guzzle:^8.0.0;写^8.0或8可能匹配不到元数据 - 输出是倒序的:最后一行是你
composer.json的根声明,往上每行末尾的(required by)就是真正提出互斥约束的包 - 输出为空?先检查
require-dev——phpunit/phpunit、orchestra/testbench这类测试工具常拖着旧版sebastian/exporter或symfony/console,间接锁死主依赖
看清真实依赖结构,别信 composer.json 的“愿望清单”
composer.json 是你写的声明,composer show --tree 才是当前 vendor/ 和 composer.lock 里的实际快照:
- 看到
(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 update 默认拒绝更新子依赖,哪怕新版本根本跑不起来:
- 正确写法:
composer update monolog/monolog --with-dependencies—— 只动它和直系依赖 - 错误写法:
composer update "monolog/monolog:^3"—— 引号 +^会让 Composer 自己找“最新”,可能跳过必要联动升级 - 想降级到精确版本(如从 Guzzle 8 切回 7.4.5):先在
composer.json里写死"guzzlehttp/guzzle": "7.4.5",再执行composer update guzzlehttp/guzzle --with-dependencies
别碰 composer.lock 文件本身
它不是配置文件,而是依赖图的哈希快照。手动编辑一个字段,下次 install 就会失败:
- Git 合并冲突?运行
composer update --lock自动重建哈希并清理冲突标记,别删整个文件 - 镜像没生效?检查键名是不是写成
repos.packagist(应为repo.packagist),或 URL 末尾有没有漏斜杠 - 全量
composer update容易触发 SAT 求解器卡死,尤其依赖多的项目;优先用--dry-run验证:composer require laravel/framework:11.0.0 --dry-run
最常被忽略的是 require-dev 里的隐性锁死效应,以及 --with-dependencies 的强制必要性——漏掉这两点,90% 的“升级后报错”其实都本可避免。











