真正能落地的模块化是用path仓库+精准psr-4映射+接口契约实现物理隔离;直接composer require业务模块会因依赖框架上下文、autoload冲突、容器未注册等导致Class not found或绑定异常。

不能靠 composer require 拆单体,也不能用 composer split(这命令根本不存在)——真正能落地的模块化,是用 path 仓库 + 精准 psr-4 映射 + 接口契约,在物理隔离前提下保持开发连贯性。
为什么直接 composer require 业务模块会崩?
业务模块不是工具库,它活在框架上下文里:依赖 Request、Auth、数据库连接、事件总线、配置文件。强行发包到 Packagist 或私有源,等于把运行时环境也打包了,结果就是:
-
Class not found或IlluminateContractsContainerBindingResolutionException频发,因为 vendor 包没注册到 Laravel 容器 - 每次改
src/OrderService.php都得composer update+ 清缓存 + 重启队列,本地开发像踩刹车 -
autoload-dev和主项目psr-4映射重叠,composer dump-autoload后类加载失败是常态 - 测试无法只加载“订单模块”,必须启动整个 Web 应用,CI 跑一次测试动辄 3 分钟起
怎么用 path 仓库实现物理隔离?
不发包、不上传、不打 tag,靠 Composer 的 path 类型软链接本地目录,自动加载走 psr-4,但生命周期完全由主应用掌控:
- 在根目录
composer.json的repositories里加:{"type":"path","url":"./modules/order"} - 对应模块的
composer.json必须定义autoload,且路径末尾不加斜杠:{"psr-4":{"App\Modules\Order\":"src/"}}(写成"src/"❌,会多拼一次/导致文件找不到) -
require写成"app/module-order":"*",执行composer update app/module-order后,AppModulesOrder就可直接use和new - 修改
./modules/order/src/下任意文件,无需 reload、无需dump-autoload,改完即生效
模块间通信为什么必须用接口契约?
一个模块调另一个模块的逻辑,如果写成 new App\Modules\Inventory\StockChecker() 或 InventoryFacade::check(),那模块就废了——耦合死,没法单独测试,也没法换实现:
- 所有外部依赖(如库存服务、通知渠道)必须通过接口注入,比如
InventoryServiceInterface - 契约定义必须放在独立抽象包里(如
app-contracts),不能塞进任一业务模块 - 模块内禁止出现
Illuminate\Support\Facades\XXX,否则一上线就报容器绑定失败 - 模块自己的
autoload必须收敛到自身命名空间,绝不能写"App": "modules/order/src"这种宽泛映射
autoload 性能卡点在哪?
不是 composer dump-autoload -o 不够快,而是 psr-4 配置太宽泛导致扫描爆炸。真实瓶颈常出现在:
-
"App": "src/"这种映射会让自动加载器遍历整个src/目录树,哪怕里面混着migrations、config、tests - 路径末尾多写斜杠(如
"MyApp\User\": "modules/user/src/")会导致 Composer 拼出modules/user/src//User.php,文件找不到 - 多个模块共用同一段 autoload 配置,容易触发命名空间冲突或覆盖
最易被忽略的是:模块的 composer.json 里不能带 require-dev,否则测试框架依赖会污染主项目;组件包也不能硬编码引用主项目命名空间,否则抽离复用立刻失效。


















