Composer卡在Resolving dependencies是循环依赖实锤,表现为CPU拉满、无网络请求;用composer update --dry-run -v观察路径重复、composer depends --tree定位闭环链路,并检查autoload隐式循环,破环需抽离契约包、运行时解耦或降级为可选依赖。

报错卡在 Resolving dependencies 超过 30 秒,就是循环依赖实锤
Composer 不会报“检测到循环依赖”,它直接卡住、CPU 拉满、无网络请求——这是 SAT 求解器在死循环回溯。别等它自己退出,立刻执行 composer update --dry-run -v,重点看最后几行是否反复出现同一路径,比如:vendor/a → vendor/b → vendor/a。出现两次即闭环确认。
注意:composer show --tree 默认包含 require-dev,容易干扰判断;composer validate 对依赖逻辑完全无效,别用它诊断。
用 composer depends --tree 定位真实闭环链路
这个命令是唯一能看清间接依赖的入口,但有两个硬前提:
- 项目必须已成功运行过
composer install(保证composer.lock有完整快照) - 必须加
--tree参数,否则只显示一级依赖,根本看不到环
运行 composer depends vendor/a --tree,如果输出中出现 vendor/a ← vendor/b ← vendor/a,箭头方向是“被谁依赖”,自身出现两次就是闭环。如果提示 Package not found,说明该包没进 composer.lock,先删 vendor/ 和 composer.lock,再跑 composer update --dry-run 看第一步失败点。
工作流任务拆解与模型编排技能。使用场景:(1) 收到复杂工作任务需要拆解为可执行步骤,(2) 需要为不同步骤选择最合适的模型,(3) 需要跟踪工作流进度和模型使用情况,(4) 长时间任务卡住需要问题诊断和解决方案。
检查 autoload 和 autoload-dev 引入的隐式循环
很多“循环”根本没写在 require 里,而是靠自动加载悄悄打通的:
-
autoload中声明了"psr-4": {"App\": "src/"},而src/里用了phpunit/phpunit的TestCase -
require-dev包(如phpstan/phpstan)的composer.json里写了"autoload": {"psr-4": {"App\": "../src/"}},把你的代码路径反向加进了它的加载范围 -
autoload-dev错误地把vendor/下的路径也写进去了——等于告诉 Composer:“我依赖我自己”
验证方法:临时注释掉 require-dev 下所有非核心工具(phpunit、infection、phpstan),再试 composer update;如果恢复成功,问题就在这里。
破环只有三种有效方式,没有“跳过”或“强制安装”
所有靠谱解法都指向一个目标:让依赖方向变成单向。
- 抽离契约包:把双方共用的接口、DTO、异常类拎出来建新包(如
myorg/contracts),然后myorg/core和myorg/api都只require "myorg/contracts": "^1.0",删掉彼此的require - 运行时解耦:
A不再new B()或use BSomeClass,而是定义interface ServiceInterface(放契约包里),B实现它,A通过容器或工厂获取实例——此时A的composer.json里彻底删掉对B的require - 降级为可选依赖:如果
B只在A的测试里用(比如 Mock 数据),就移到require-dev;如果是增强功能(如导出 Excel),改用suggest字段提示用户手动安装
真正难的不是定位,是说服团队接受重构——一旦依赖图里出现 A→B→A,后续每次版本升级、CI 构建、甚至 autoload 加载顺序微调,都可能突然爆掉。越早拆,成本越低。

















