composer depends --tree 显示路径断在 react/event-loop 是因其处于依赖图底部,所有 ReactPHP 组件都直接依赖它,但它不依赖其他 ReactPHP 包;输出层级浅说明位置基础,而非缺失嵌套。

composer depends --tree 显示的依赖路径为什么总断在 react/event-loop?
因为 react/event-loop 是 ReactPHP 的核心基础包,几乎所有其他 ReactPHP 组件(如 react/http、react/promise、react/socket)都直接 require 它,但它本身不依赖任何其他 ReactPHP 包——所以 composer depends --tree react/event-loop 输出的“上游”链条,只到它的直接使用者为止,不会继续向上延伸。
常见误判是看到输出里只有两三级就以为“没嵌套”,其实这恰恰说明它处于依赖图的底部位置。真正需要警惕的是反向:用 composer depends --tree react/http 才可能暴露出 react/event-loop 被间接拉入的深层路径(比如通过某个 dev-only 包绕道引入)。
- 执行前确保已运行过
composer install,否则composer.lock缺失,--tree无法还原真实安装路径 - 若输出为空,不是没有依赖,而是当前项目中没有任何包声明对
react/event-loop的直接或间接 require(可能只在require-dev中,而你未启用 dev 模式) - 加
-v参数可看到解析过程中的版本匹配决策,有助于判断是否因版本约束导致某条路径被剪枝
如何识别 react/event-loop 被 require-dev 意外带入生产环境?
这是 ReactPHP 项目中最隐蔽的循环风险点:phpunit/phpunit 或 mockery/mockery 这类测试工具,在它们自己的 composer.json 中可能通过 "autoload": {"psr-4": {"App\": "../src/"}} 把你的 src/ 目录加载进测试进程;而你的代码又用了 ReactEventLoopLoop,这就形成了「运行时依赖闭环」——虽然 composer 不报错,但实际执行时会因自动加载冲突或类重复定义崩溃。
验证方法很简单:
立即学习“PHP免费学习笔记(深入)”;
- 运行
composer install --no-dev后检查vendor/composer/autoload_psr4.php,确认里面是否还存在指向你项目目录的映射(若有,说明某个 dev 包把你的 autoload 泄露出去了) - 临时删掉
require-dev下所有包,再跑composer depends --tree react/event-loop,对比前后差异 - 重点检查
phpunit/phpunit的 composer.json 是否含"autoload"字段引用了项目路径(2026 年主流 PHPUnit 9.x 版本已收敛此问题,但定制 fork 或旧分支仍可能保留)
为什么 composer depends 不显示流式 I/O 组件(如 react/stream)对 event-loop 的依赖?
因为 react/stream 并不直接 require react/event-loop ——它只声明了 "suggest",表示“建议搭配使用”,而非硬性依赖。Composer 的 depends 命令只追踪 require 和 require-dev 字段,完全忽略 suggest、provide、conflict 等元信息。
这意味着:即使你装了 react/stream 却没装 react/event-loop,composer install 也不会报错;但一旦调用 StreamSelectLoop 或 Loop::get(),就会抛出 Class 'ReactEventLoopLoop' not found。
- 不要依赖
composer depends来验证运行时完整性,它只反映声明依赖,不反映实际使用逻辑 - ReactPHP 官方文档明确要求:只要用到任何 ReactPHP 组件,就必须显式
require react/event-loop - CI 流程中建议加一条检查:grep -q "React\\EventLoop" vendor/autoload.php || echo "event-loop missing!"
嵌套深度超过 5 层时,event-loop 的启动行为会受影响吗?
不会。ReactPHP 的事件循环本身不关心自己被多少层包间接引入,Loop::get() 总是返回单例,$loop->run() 也只启动一次。所谓“嵌套深度”只是 Composer 解析依赖图时的路径长度,和运行时性能、内存占用、事件分发机制毫无关系。
真正影响启动行为的,是以下三点:
- 是否在
require中显式声明了react/event-loop(否则Loop::get()可能返回 null 或触发 autoloader 失败) - 是否有其他包(如某些旧版
amphp/amp)也注册了全局事件循环单例,造成冲突 -
composer.lock中记录的react/event-loop版本是否与所用 ReactPHP 组件兼容(例如react/http ^1.0需要react/event-loop ^1.0,而^2.0不向下兼容)
深度本身不是问题,问题在于深度背后隐藏的版本错配或实现混用——比如 react/http 用 ExtEvLoop,而你手动 new 的 StreamSelectLoop 被另一个包悄悄替换了全局实例。



















