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

报错“Your requirements could not be resolved”不是网络或缓存问题,是 composer.json 里写的约束之间存在逻辑矛盾——删 vendor 或重跑 composer update 只会让问题更难定位。
看到 “Conclusion: don’t install xxx” 就该立刻跑 composer why-not
这行红字是结论,不是原因。真正要查的是“谁在阻止这个版本被选中”。composer why-not 不看当前装了什么,只检查所有已声明的约束(包括 require-dev)是否允许该版本存在。
- 必须带完整版本号,比如
composer why-not symfony/console:^6.4.0,写^6.4可能匹配不到元数据 - 输出是从底往上读的:最后一行是你自己的
composer.json声明,往上逐行看哪条required by提出了互斥约束,那就是冲突源头 - 如果输出第一行就出现
abandoned包,说明它已被作者停更,得去 Packagist 页面查推荐替代包,而不是硬升版本 - 输出为空?先检查是不是写错了版本号,或者该版本在当前
minimum-stability下不可见
require-dev 是最常被忽略的冲突源
从 Composer 1.7 开始,require-dev 全程参与依赖解析——哪怕你执行的是 composer install --no-dev,它的约束也会影响主依赖树构建。
工作流任务拆解与模型编排技能。使用场景:(1) 收到复杂工作任务需要拆解为可执行步骤,(2) 需要为不同步骤选择最合适的模型,(3) 需要跟踪工作流进度和模型使用情况,(4) 长时间任务卡住需要问题诊断和解决方案。
- 典型场景:主项目要求
laravel/framework: ^11.0,但orchestra/testbench: ^7.0(在require-dev中)强制拉入laravel/framework: ^10.0,整个依赖树就被锁死 - 运行
composer why symfony/console,确认它是否被某个 dev 工具间接引入 - 临时注释掉
require-dev中非必需的包,再跑composer update --dry-run验证是否能过 - 别信“只是开发用,不影响生产”——Composer 解析时不管 dev 还是 prod,所有约束一视同仁
用 composer show --tree 看真实依赖结构
composer.json 是你写的“愿望清单”,composer show --tree 才是 vendor/ 和 composer.lock 里的真实快照。它能暴露你根本没意识到的间接路径。
- 查某个包被谁引入:
composer show --tree | grep "symfony/console" - 过滤关键段:
composer show --tree monolog/monolog | grep -A5 -B5 "guzzlehttp/guzzle" - 注意括号里的
(locked to 5.4.42)——这说明该版本已被composer.lock锁定,不是当前composer.json声明决定的 - 看到
(provided)或(replaced),得去那个包自己的composer.json里确认它到底替代了什么、是否真能覆盖
升级后必须验证三件事,否则问题会延迟暴露
依赖变了,但项目不会自动告诉你哪里坏了。很多问题要等缓存生效、容器编译、自动加载重建后才暴露。
- 强制重建自动加载:
composer dump-autoload -o,否则新增类可能根本找不到 - 清空所有缓存:
php bin/console cache:clear --env=dev(Symfony)或对应框架的缓存命令,别只清dev环境 - 检查配置兼容性:
php bin/console config:dump-reference或php bin/console lint:container,尤其升级框架后,配置键名或默认值可能已变
最危险的操作是手动编辑 composer.lock——它不是配置文件,而是依赖图的哈希快照。改错一个 hash 或 version 字段,下次 install 就会失败。复杂点在于:冲突往往藏在 require-dev 里,而很多人连 why-not 的输出都懒得倒着读。










