“Resolving dependencies”卡住不是死锁,而是SAT求解器因约束互斥陷入无解回溯;应优先用composer require --dry-run -v定位冲突枢纽,再通过composer prohibits和composer show分析阻断链,避免盲目删lock文件。

require卡在“Resolving dependencies”不是死锁,是解析失败
Composer require 命令卡住不动,尤其停在 Resolving dependencies 阶段,95% 不是系统级死锁,而是 SAT 求解器陷入无解状态——它在穷举所有版本组合,但找不到一组满足全部约束的解,最终耗尽 CPU 和时间后静默卡住(不报错、不退出)。这和数据库死锁或 flock() 互等完全不同。
常见诱因包括:
- 新包与现有
require-dev工具链冲突(如phpunit/phpunit锁了symfony/console ^5.4,而你要装的包只兼容^6.0) -
composer.json里写了互斥约束,例如同时存在"monolog/monolog": "2.9.0"和"guzzlehttp/guzzle": "^3.0"(后者已废弃且与 monolog 2.9 不兼容) - 本地 PHP 版本低于某个包的
php平台要求,但又没设"platform",导致 Composer 在解析时反复回溯却无法排除该包
验证方法:加 -v 运行,看最后几行是否出现 Dependency resolution completed —— 如果一直不出现,基本就是无解卡死。
用 --dry-run -v 强制摊开求解过程
别等它自己卡完,直接用 composer require vendor/package --dry-run -v。这个命令不改任何文件,但会强制 Composer 输出完整推理链,关键信息集中在末尾:
- 找
Found conflicting requirements for行,它标出第一个爆掉的包 - 盯住反复出现的包名(如
symfony/event-dispatcher),大概率是冲突枢纽 - 注意
Root requirements段落,它指出是你composer.json里哪一行触发了整条链
如果输出太长,用 tail -n 50 快速截取末尾;若仍看不清,再补上 --no-dev 试试:composer require vendor/package --dry-run -v --no-dev,快速判断是否 dev 工具拖累。
为什么 composer why-not 有时查不到拦路包?
composer why-not 只能查「你明确指定的包+版本」是否被其他包拒绝,但它不会告诉你「哪个包正在把某个间接依赖压向不可解区间」。比如你想装 laravel/framework:11.0,why-not laravel/framework:11.0 可能返回空——因为没人直接 conflict 它,而是 spatie/laravel-ray 要求 illuminate/support ^10.0,而 illuminate/support ^10.0 又和 laravel/framework:11.0 的 illuminate/support ^11.0 冲突。
这时必须换策略:
- 先运行
composer prohibits illuminate/support:^11.0,找出谁在拦^11.0 - 再对拦路包(比如
spatie/laravel-ray)运行composer show spatie/laravel-ray,看它requires和conflicts字段 - 检查该包最新版是否已支持 Laravel 11,若未支持,就得等它发版或临时 fork 修复
删 lock 文件前务必先备份并确认当前状态
看到卡死就删 composer.lock 是最危险操作——它抹掉了当前已知「能跑」的依赖快照,让 Composer 从零开始瞎试,反而更容易陷入更深的无解循环。
正确做法是:
- 先执行
git status确认composer.lock没被意外修改过 - 用
composer show --tree | head -30快速扫一眼当前依赖树是否健康(有没有重复包名出现在不同层级但版本一致) - 真正要删,也先
cp composer.lock composer.lock.bak备份,再删 - 删完立刻跑
composer install --dry-run,验证是否真能重新生成可行解——如果还卡,说明问题不在 lock,而在约束本身
真正难缠的点往往藏在 require-dev 的 autoload 跨目录加载里,比如 phpunit/phpunit 的 autoload 把你的 src/ 当成它自己的命名空间去加载,造成隐式反向依赖,这种问题连 --dry-run -v 都不会直接报错,得靠临时注释掉整个 require-dev 区块来交叉验证。











