判断依赖是否该抽成独立包,关键看是否同时满足:被多个模块高频复用、有明确稳定接口、不绑定主应用具体实现;适合抽包的如通知通道抽象、订单状态机,不适合的如硬编码的ExcelExporter。

单体项目里共享依赖(比如 psr/log、symfony/http-foundation)不是“拆”出来的,而是通过识别高内聚模块 + 提取抽象契约 + 独立发布来解耦——你真正要拆的是代码边界和依赖流向,不是 Composer 自动加载的路径。
怎么判断一个依赖该不该被单独抽成包?
关键看它是否同时满足:被多个业务模块高频复用、有明确稳定接口、不绑定主应用具体实现(如 Laravel Facade、config() 调用、app() 容器直取)。
- ✅ 适合抽包:通用通知通道抽象(
NotificationSenderInterface)、订单状态机核心逻辑、多租户上下文管理器 - ❌ 不适合抽包:某个 Controller 里硬编码 new 出来的
ExcelExporter、只在用户模块用到的密码强度校验器 - ⚠️ 风险点:如果包内部 require 了
laravel/framework或illuminate/database,它就很难被其他非 Laravel 项目复用,也容易因主应用升级而断裂
如何用 Composer 管理多个子模块的依赖?
不用强行给每个模块配独立 composer.json,而是用 path 类型仓库 + 主项目统一协调。这是目前最可控、调试成本最低的方式。
- 把模块目录(如
packages/auth)当成独立包,里面放自己的composer.json,含name(如myorg/auth)、autoload.psr-4和最小化require - 主项目
composer.json的repositories加一条:{"type": "path", "url": "./packages/*"} - 然后
require它:"myorg/auth": "dev-main"(开发期用分支名,发布后切为"^2.0") - 运行
composer update myorg/auth即可局部更新,不影响其他模块
为什么不能直接用 composer install --no-dev 在子模块里单独装依赖?
因为 Composer 的自动加载是全局生效的,子模块的 vendor/ 不会被主项目自动识别;而且一旦多个模块 require 同一个底层库(如 guzzlehttp/guzzle),就会出现两份类定义,触发 Fatal error: Cannot declare class。
- 正确做法:所有运行时依赖都声明在主项目
composer.json中,子模块只声明「对抽象的依赖」(如psr/container) - 开发期工具类(如 PHPStan、PHP-CS-Fixer)可放在子模块的
require-dev,但必须确保它们不被主项目 autoload 到 - 若子模块真需要隔离依赖(如测试用 MockServer),应走 Docker 或进程级隔离,而非 Composer 层面
最容易被忽略的一点:抽包之后,composer dump-autoload -o 必须在主项目执行,而不是进子目录跑——否则 PSR-4 映射不会合并进去,类就找不到。这也意味着,模块间的命名空间引用必须真实存在、不能靠 IDE 虚拟补全蒙混过关。











