Composer本身只是PHP依赖管理工具,不能直接实现业务模块化;强行将业务逻辑打包为Composer包会导致耦合、加载冲突与生命周期失控,真正可行的是通过path仓库类型+PSR-4命名空间+接口契约实现物理隔离与松耦合。

Composer 本身不提供模块化架构能力,它只是 PHP 的依赖管理工具;想靠 composer require 把巨石应用“拆成模块”,会直接掉进包耦合、自动加载冲突、生命周期失控的坑里。
为什么不能把业务逻辑直接打包成 Composer 包?
业务模块不是 SDK 或工具库,它往往依赖具体框架上下文(如 Laravel 的 Request、Auth、数据库连接)、共享配置和事件总线。强行发布为 composer install 可安装的包,会导致:
- 每次修改都要
composer dump-autoload+ 清缓存,本地开发体验极差 -
autoload-dev和psr-4映射容易重叠,Class not found错误频发 - 无法共用同一份
.env、中间件栈、队列连接,实际运行时抛出IlluminateContractsContainerBindingResolutionException - 测试时难以隔离——你没法只测“订单模块”而不启动整个 Web 应用
真正可行的模块切分方式:按 PSR-4 命名空间 + 本地路径加载
不发布到 Packagist,也不走 composer require,而是用 Composer 的 repositories + path 类型,在 composer.json 中声明本地目录为“包源”:
{
"repositories": [
{
"type": "path",
"url": "./modules/order"
},
{
"type": "path",
"url": "./modules/inventory"
}
],
"require": {
"app/module-order": "*",
"app/module-inventory": "*"
}
}
然后在各模块的 composer.json 中明确定义 autoload:
{
"autoload": {
"psr-4": {
"App\Modules\Order\": "src/"
}
}
}
执行 composer update 后,这些命名空间就可被自动加载,且保持物理隔离——./modules/order/src/ 下的代码完全不感知 Laravel 入口,只依赖接口契约(如 AppContractsInventoryService)。
工作流任务拆解与模型编排技能。使用场景:(1) 收到复杂工作任务需要拆解为可执行步骤,(2) 需要为不同步骤选择最合适的模型,(3) 需要跟踪工作流进度和模型使用情况,(4) 长时间任务卡住需要问题诊断和解决方案。
模块间通信必须通过接口 + 服务容器绑定
禁止跨模块直接 new 实例或调用静态方法。所有依赖都应声明为接口,并在主应用的 AppServiceProvider::register() 中绑定:
$this->app->bind(InventoryService::class, function ($app) {
return new AppModulesInventoryServicesDefaultInventoryService(
$app->make(InventoryRepository::class)
);
});
关键点:
- 接口定义放在主应用的
app/Contracts/,实现放在对应模块的src/下 - 模块内部不允许引用其他模块的
AppModulesXxx具体类,只允许依赖AppContracts* - 数据库迁移、命令、事件监听器等“胶水代码”仍保留在主应用中,模块只提供核心逻辑和接口
CI/CD 和部署时的陷阱
本地 path 加载在 CI 环境下会失效——因为 ./modules/order 路径可能不存在,或权限不对。解决方案只有两个:
- 开发阶段用
path,上线前用脚本将各模块git subtree push到独立仓库,再改回packagist类型依赖(推荐) - CI 构建时用
rsync或cp -r复制./modules/*到 vendor 目录并手动注册 autoload(不推荐,破坏 Composer 语义)
最常被忽略的是:模块的单元测试必须能脱离框架单独跑通,否则所谓“独立”只是假象——检查你的 phpunit.xml 是否仍加载了 tests/TestCase.php 这种 Laravel 特有基类。

















