本文目录导读:

- 物理边界:命名空间与目录结构(基础)
- 依赖边界:Composer 包管理(最强制)
- 逻辑边界:接口(Interface)与依赖倒置
- 领域边界:DDD(领域驱动设计)战术模式
- 单一职责边界:文件与类设计
- 实施建议:如何落地?
- 边界的本质
在 PHP 中划分模块边界是一个架构设计问题,核心目标是降低耦合、提高内聚,PHP 不像 Java 有强制性的 package 或 module 机制,但可以通过命名空间、目录结构、Composer 依赖、接口契约以及分层架构来划分边界。
以下是 PHP 项目划分模块边界的完整策略,从基础到进阶:
物理边界:命名空间与目录结构(基础)
这是最直接的边界划分,使用 PSR-4 自动加载规范,让命名空间与目录一一对应。
- 原则:按业务域(Domain) 划分,而不是按技术类型(Controller, Model)划分。
- 反模式:
app/Controllers/、app/Models/(按技术划分)。 - 推荐模式:
app/Modules/User/、app/Modules/Order/。
app/
├── Modules/
│ ├── User/
│ │ ├── Controllers/
│ │ ├── Services/
│ │ ├── Repositories/
│ │ └── Models/
│ ├── Payment/
│ │ ├── Controllers/
│ │ ├── Services/
│ │ └── Gateways/
│ └── Order/
│ └── ...
└── Shared/ // 核心共享层(基础设施)
└── Traits/
依赖边界:Composer 包管理(最强制)
将模块拆分为独立的 Composer 包,是 PHP 中最严格的边界划分方式。
- 实施:在
composer.json中使用"autoload"配置映射各自的命名空间。 - 约束:
Order模块需要依赖Payment模块,必须显式在Order模块的composer.json中声明"require",如果未声明,而代码直接使用了Payment的类,IDE 和静态分析工具(如 PHPStan)会立即报警。 - 内部包:即使不发布到 Packagist,也可以在根项目的
repositories中配置path类型,将它们作为本地 VCS 包管理。
逻辑边界:接口(Interface)与依赖倒置
这是解耦的核心,模块之间不应依赖具体类,而应依赖接口。
- 策略:定义接口在消费方(使用方),实现类在提供方。
- 场景:
Order模块需要计算运费,但运费逻辑在Shipping模块。// 边界定义(在 Order 模块中) interface ShippingCalculatorInterface { public function calculateCost(Order $order): Money; }Order模块的 Service 只注入这个Interface,具体的FedExCalculator在Shipping模块中实现,并通过容器(如 Laravel的 ServiceProvider)绑定注入。 结果:Order模块不知道Shipping模块的存在。
领域边界:DDD(领域驱动设计)战术模式
对于复杂业务,按 DDD 的 Bounded Context(限界上下文)划分。
- 划分:每个模块(如:销售、库存、财务)都是独立的限界上下文。
- 核心规则:
- 模块之间禁止共享数据库表(物理隔离)。
- 模块之间禁止直接调用对方的 Repository。
- 只能通过 Domain Events(领域事件) 或 Application Service 进行通信。
- 数据同步通过事件(如
OrderPlaced事件触发Inventory模块扣减库存)。
单一职责边界:文件与类设计
在同一个模块内部,也要划分清晰的职责边界,避免上帝类。
- Action 模式:一个类只做一件事。
User模块下拆分为RegisterUser、UpdateUserProfile、ChangePassword三个 Action 类,而不是一个UserService处理所有。 - DTO(数据传输对象):跨模块传参时,不要直接传递 Eloquent Model 或 Entity,应传递轻量级 DTO,防止模块 A 的实体修改直接污染模块 B 的逻辑。
实施建议:如何落地?
如果你有一个现有的混乱项目,建议按以下步骤逐步推进:
-
第一步:梳理显式依赖 检查
use语句,找出耦合点,确定哪些模块可以合并,哪些必须拆分。 -
第二步:定义接口层 禁止模块 A 直接
new模块 B 的类,引入Interface和依赖注入容器(PSR-11)。 -
第三步:建立“防腐层”(Anti-Corruption Layer) 如果无法完全隔离数据,在模块 A 和 B 之间建立一个转换层,负责翻译双方的模型和数据结构。
-
第四步:利用工具强制约束
- Deptrac:这是一个 PHP 静态分析工具,专门用来定义和检查模块依赖规则,你可以在
deptrac.yaml中声明“Order模块不允许依赖Inventory模块”,并集成到 CI,若违反则构建失败。 - PHPStan / Psalm:配合
@api注解或--level=8严格检查未定义的依赖。
- Deptrac:这是一个 PHP 静态分析工具,专门用来定义和检查模块依赖规则,你可以在
边界的本质
PHP 的模块边界不仅是代码层面的物理隔离,更是团队之间的口头协议。
- 顶层(目录):决定代码放在哪里。
- 中间层(Composer + Interface):决定谁允许知道谁。
- 底层(事件/异步消息):实现最终一致性和物理解耦。
最简单的判断标准:如果修改 User 模块的内部代码,会导致 Order 模块的测试失败,那么这两个模块的边界就不清晰,需要重构。