Composer遇到循环依赖直接报错中止,不绕过、不降级、不忽略;必须用composer depends --tree定位闭环,再通过抽离契约包、运行时解耦或降级为可选依赖三种方式重构实现单向依赖。

Composer 一看到依赖图里存在闭环(比如 A → B → A),就立刻中止解析并报错,不尝试绕过、不降级、不忽略——它把循环依赖当作架构缺陷信号,而不是安装故障。
报错信息里出现 Package a depends on b, which depends on a 就是实锤
这不是版本没选对,也不是缓存或网络问题。常见错误形式包括:
Root package cannot be installedYour requirements could not be resolved to an installable set of packages- 执行
composer update卡在Resolving dependencies超过 30 秒后才失败
注意:这些错误常伪装成“版本冲突”,但真正根源是闭环。用 composer update --dry-run -v 看详细日志,如果末尾反复出现同一组包来回引用(如 vendor/a → vendor/b → vendor/a),基本可以确认。
必须用 composer depends --tree 定位闭环路径
这个命令是唯一靠谱的诊断入口,但它有硬性前提:
- 必须在已成功运行过
composer install的项目里执行,否则依赖图不完整 - 必须加
--tree参数,否则只显示一级依赖,看不到闭环 - 若提示
Package not found,说明该包根本没进composer.lock,可能被早期拦截了——先删掉vendor/和composer.lock,再跑composer update --dry-run观察第一步失败点
例如怀疑 myorg/core 被循环引入,就运行:composer depends myorg/core --tree
输出中若出现 myorg/core ← myorg/api ← myorg/core,箭头方向表示“被谁依赖”,自身出现两次即闭环。
工作流任务拆解与模型编排技能。使用场景:(1) 收到复杂工作任务需要拆解为可执行步骤,(2) 需要为不同步骤选择最合适的模型,(3) 需要跟踪工作流进度和模型使用情况,(4) 长时间任务卡住需要问题诊断和解决方案。
最容易被忽略的是 autoload + require-dev 引发的隐式循环
很多“循环”根本没写在 require 字段里,而是靠自动加载悄悄打通的。典型场景:
-
autoload中声明了"psr-4": {"App\": "src/"},而src/里用了phpunit/phpunit的类(比如TestCase) -
require-dev包(如phpstan/phpstan)的autoload字段里包含了你项目的路径(如"../src") -
autoload-dev错误地把vendor/下的路径也加进来了
这类循环不会出现在 composer show --tree 里,但会真实阻塞解析。验证方法:临时注释掉 require-dev 下所有非核心工具(phpunit、infection、phpstan 等),再试一次 composer update。如果恢复成功,问题就在这里。
真正有效的破环方式只有三种,没有“跳过”选项
所有靠谱解法都指向一个目标:让依赖方向变成单向。
-
抽离契约包:把共用的接口、DTO、异常类拎出来建新包(如
myorg/contracts),它的composer.json中"require": {}应为空;core和api都只require "myorg/contracts": "^1.0" -
运行时解耦:A 不再
new B()或use BClass,而是定义LoggerInterface(放契约包),B 实现它,A 通过容器获取实例;此时 A 的composer.json必须删掉对 B 的require -
降级为可选依赖:如果 B 只在 A 的测试中用,移到
require-dev;如果是增强功能(如导出 Excel),改用suggest字段提示用户手动安装
别碰 --ignore-platform-reqs 或手动改 vendor/ —— 这些只是掩盖问题,下次 composer update 还会崩。真正的难点往往不在怎么拆,而在于识别那些藏在 autoload 和 require-dev 里的逻辑闭环,它们不会报错,但会让整个依赖图失效。

















