循环依赖在 composer 中会导致 composer update 直接失败,需用 composer depends --tree 检测闭环路径;require-dev、path 仓库配置、autoload-dev 路径是高发区;应通过 contracts 抽象+replace 替代硬引用,而非忽略报错。

开发 Composer 扩展包时,循环依赖不是“可能遇到”的问题,而是只要两个包互相 require 对方,就会在 composer update 阶段直接失败——它不会装、不给提示、不妥协。
怎么一眼识别正在写的包已经埋了循环依赖
写完 composer.json 后别急着 composer update,先跑这行命令:
composer depends --tree vendor/your-vendor/your-package
如果输出里出现 your-vendor/your-package ← your-vendor/core ← your-vendor/your-package 这类自身被自己反向依赖的路径,闭环已成立。常见但容易忽略的场景:
- 你在
your-package的require里写了"your-vendor/core": "^2.0",而core的autoload-dev或测试用例里又通过../../your-package/src把你的包反向加载进来了 -
core的require-dev包含了phpunit/phpunit,而它的autoload配置里有"psr-4": {"Tests\": "tests/"},你又在your-package的tests/下写了use CoreSomething;——Composer 会把整个vendor/当作可加载源,间接拉通双向路径 - 你用
path类型仓库本地开发,repositories里配了"url": "../core",但没加末尾/,导致 Composer 把../core和../core/当成两个不同源,反复 fallback 并误判依赖关系
require-dev 是循环依赖最常藏身的地方
很多开发者只盯着 require,却忘了 require-dev 同样参与依赖图构建。一旦某个 dev 包(比如 phpstan/phpstan 或 infection/infection)在自己的 autoload 里声明了 ../ 或 ../../ 路径,它就等于把你的项目代码“反向注入”进依赖链。
验证方法:临时删掉 composer.json 里的全部 require-dev 条目,再跑 composer update --dry-run -v。如果卡住现象消失,问题八成出在这里。
围绕关键发现、作用机制、临床相关性及研究局限性展开讨论。适用于撰写或优化任何生物医学论文的“讨论(Discussion)”部分——包括结果解读、与既往文献关联、阐释意外发现、界定研究局限性,以及撰写结论。当用户输入以下任一指令时也会自动触发该功能: - “write my discussion” - “help me discuss my findings” - “how do I compare to prior studies” - “write the limitations par
更稳妥的做法是:
- 所有本地 path 仓库,URL 必须以
/结尾:"url": "../core/",不是"../core" -
autoload-dev里禁止出现任何指向vendor/或兄弟目录(如../other-package)的路径 - CI 流水线中用
COMPOSER_DEV_MODE=0 composer install --no-dev,确保生产环境行为和开发环境解耦
用 replace + contracts 拆掉硬耦合,而不是绕开报错
当你发现 core 和 plugin-sdk 确实需要互相调用,又不能合并成一个包,别用 "minimum-stability": "dev" 或 --ignore-platform-reqs 强过——这些只是掩盖问题。
正确做法分两步:
- 新建一个极轻量的
your-vendor/contracts包,只含接口、DTO、简单 trait,autoload仅 PSR-4 映射到src/,不带任何实现 - 在
core和plugin-sdk的composer.json中都移除对彼此的require,改为:"require": {"your-vendor/contracts": "^1.0"} - 在
core的composer.json中加:"replace": {"your-vendor/plugin-sdk": "*"},告诉 Composer:“我内置 SDK 能力,别再装它”
注意:replace 不影响运行时逻辑,只作用于安装阶段;被 replace 的包仍可在 require-dev 中显式列出,用于本地测试。
为什么删 lock 文件 + 重 install 解不了循环依赖
因为循环依赖是依赖图结构问题,不是缓存或锁文件损坏。删 composer.lock 只会让 Composer 重新走一遍解析流程,而 SAT 求解器遇到 A→B→A 闭环时,依然会卡死或报错。真正要动的是包之间的引用关系本身——要么抽离第三层抽象,要么用 replace 主动切断其中一环。没有“跳过检测”的合法方式,也没有“降级版本就能绕开”的侥幸空间。










