composer 遇到循环依赖会直接报错或 cpu/内存暴涨致卡死,根本原因是 sat 求解器无法对环状依赖图拓扑排序;破环唯一有效方式是抽离契约包、运行时解耦或降级为 require-dev。

Composer 遇到循环依赖不会卡住然后悄悄装上,它要么直接报错退出,要么 CPU 拉满、内存暴涨、终端无响应——这不是慢,是求解器在闭环里无限打转。
循环依赖会导致 Composer 安装/更新彻底失败
错误现象不是模糊的“版本冲突”,而是明确指向逻辑不可解:
Root package cannot be installedPackage a depends on b, which depends on a- 执行
composer update --dry-run -v后,末尾反复出现同一组包路径(如vendor/a → vendor/b → vendor/a) -
composer install卡在Resolving dependencies超过 30 秒,top显示 PHP 进程 CPU 持续 95%+,内存每秒涨 50MB+
根本原因:Composer 的 SAT 求解器无法对存在环的依赖图做拓扑排序。它不尝试绕过、不降级、不忽略——一旦检测到不可解结构,就中止解析。
隐式循环比显式 require 互引更难定位
你没在 require 里写 "myorg/api": "dev-main",但照样爆红。最常被忽略的是 autoload + require-dev 组合引发的运行时闭环:
围绕关键发现、作用机制、临床相关性及研究局限性展开讨论。适用于撰写或优化任何生物医学论文的“讨论(Discussion)”部分——包括结果解读、与既往文献关联、阐释意外发现、界定研究局限性,以及撰写结论。当用户输入以下任一指令时也会自动触发该功能: - “write my discussion” - “help me discuss my findings” - “how do I compare to prior studies” - “write the limitations par
- 某个
require-dev包(如phpunit/phpunit)在自己的composer.json中写了"autoload": {"psr-4": {"App\": "../src/"}},把你的src/加进了它的自动加载路径 - 而你的代码又用了它的类(比如继承
TestCase),Composer 就会把它当作生产依赖来解析 - 你在自己的
autoload-dev中误加了vendor/下的路径,等于声明“我依赖我自己” -
composer depends --tree查不到这个环——因为它是运行时加载触发的,不在composer.lock的静态依赖图里
验证方法:临时注释掉 require-dev 下所有非核心工具(phpunit、phpstan、infection),再跑 composer update。恢复了,问题就在这里。
真正有效的破环方式只有三种,没有“跳过”选项
所有临时手段(删 composer.lock、加 --ignore-platform-reqs、改 minimum-stability)都只是掩盖症状,下次更新照样崩。必须从结构上切断闭环:
-
抽离契约包:新建
myorg/contracts,只放接口、DTO、异常,"require": {}为空;myorg/core和myorg/api都只require "myorg/contracts": "^1.0",彼此require彻底删除 -
运行时解耦:A 不再
new B()或use BClass,而是定义ServiceInterface(放在契约包),B 实现它,A 通过容器获取实例;此时 A 的composer.json中必须删掉对 B 的require -
降级为可选依赖:如果 B 只在 A 的测试中用(如 Mock 工具、断言库),移到
require-dev;如果是导出 Excel 等运行时功能,则不能放require-dev
复杂点在于:很多环藏在 autoload 路径和 require-dev 的交互里,既不报错也不进 composer.lock,只能靠人工排查和临时注释验证。别指望命令自动告诉你哪一行 autoload 写错了——那是架构设计阶段该守住的边界。










