应使用 composer-merge-plugin 动态合并多 composer.json 配置,而非手动拼接或 symlink;它在 install/update 时运行时注入,支持 autoload/require 等字段合并,但需注意加载顺序、覆盖规则及 Composer 2.3+ 兼容性问题。

composer.json 不能直接合并,但可以用 merge-plugin 模拟
Composer 原生不支持多个 composer.json 文件自动合并,强行拆分或拼接会导致 autoload 错乱、依赖冲突或 composer install 失败。真正可行的路径是借助社区维护的 composer-merge-plugin —— 它在 composer install 和 composer update 阶段动态合并配置,不是文件拼接,而是运行时注入。
常见错误现象:Class not found、Package not found、autoload is not properly configured,往往是因为手动复制粘贴多个 composer.json 片段后,没重跑 composer dump-autoload,或忽略了 merge-plugin 的加载顺序。
- 必须在主
composer.json的require中先声明wikimedia/composer-merge-plugin - 插件配置写在
extra.merge-plugin下,不是根级字段 - 被合并的子配置(如
modules/*/composer.json)只保留autoload、require、repositories等允许合并的字段;name、version、type这类元信息会被忽略或报错 - 路径通配符支持
**,但要注意 shell 层是否展开——建议用引号包裹:"include": ["conf/*.json"]
为什么不用 symlink 或 git subtree?它们和 merge-plugin 不是同一层问题
有人试图用符号链接把多个 composer.json “塞”进项目根目录,或者用 git subtree 把模块 vendor 化——这会绕过 Composer 的依赖解析器,导致 composer show 看不见子模块依赖,composer update --lock 无法正确更新锁文件,甚至引发循环依赖检测失败。
真实使用场景:企业级 monorepo 中按业务域拆分模块(如 auth/、payment/),每个模块有独立 composer.json,但希望统一执行 composer install 并共享 autoload。
-
merge-plugin保持单入口,所有依赖走 Composer 原生解析流程 - symlink 方式下,
vendor/autoload.php不会自动包含子目录的 PSR-4 映射 -
git subtree导出后,子模块的composer.json变成静态快照,无法响应上游变更 - 注意:PHP 7.4+ 的
include_path或自定义 autoloader 无法替代merge-plugin对require字段的合并能力
merge-plugin 的性能开销和兼容性边界
每次 composer install 都要读取并合并所有匹配的 JSON 文件,如果子配置超过 20 个,或某个 composer.json 内含大量嵌套 require-dev,会明显拖慢命令执行速度。这不是 bug,而是设计使然——它本质上是“预处理”阶段。
兼容性上,merge-plugin 仅支持 Composer 1.x 和 2.0–2.2;Composer 2.3+ 已移除对它的官方兼容支持,会报 Plugin disabled due to unknown constraints。
- 若用 Composer 2.3+,必须改用
composer-vendor-hooks或自行实现PluginInterface,但复杂度陡增 - 子配置中避免写
scripts字段:merge-plugin不合并脚本,重复定义会覆盖而非追加 - 若子配置含
autoload-dev,主项目需显式运行composer dump-autoload --dev才生效 - 调试技巧:加
-v参数看合并日志,关键行是Merging config from ...
最易被忽略的细节:autoload 合并不是“叠加”,而是“覆盖优先级”
多个子 composer.json 若定义了相同命名空间(如都写了 "App\": "src/"),merge-plugin 默认按文件读取顺序合并——后读取的会覆盖前面的映射。这不是 bug,是明确设计行为,但文档里藏得深。
比如 modules/a/composer.json 和 modules/b/composer.json 都声明了 "App\": "src/",而 b 在 include 数组里排更后,则最终 autoload 只认 b/src/ 下的类,a/src/ 被静默丢弃。
- 解决方法:统一在主
composer.json中定义顶层 autoload,子配置只负责模块内局部映射(如"ModuleA\": "src/") - 检查方式:运行
composer dump-autoload -d --no-scripts后,打开vendor/composer/autoload_psr4.php直接看生成结果 - 别依赖 IDE 自动补全来验证——它可能缓存旧映射,一定要以实际
vendor/autoload.php行为为准










