PHP 怎么划分模块边界

wen PHP项目 5

本文目录导读:

PHP 怎么划分模块边界

  1. 物理边界:命名空间与目录结构(基础)
  2. 依赖边界:Composer 包管理(最强制)
  3. 逻辑边界:接口(Interface)与依赖倒置
  4. 领域边界:DDD(领域驱动设计)战术模式
  5. 单一职责边界:文件与类设计
  6. 实施建议:如何落地?
  7. 边界的本质

在 PHP 中划分模块边界是一个架构设计问题,核心目标是降低耦合、提高内聚,PHP 不像 Java 有强制性的 packagemodule 机制,但可以通过命名空间、目录结构、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,具体的 FedExCalculatorShipping 模块中实现,并通过容器(如 Laravel的 ServiceProvider)绑定注入。 结果Order 模块不知道 Shipping 模块的存在。

领域边界:DDD(领域驱动设计)战术模式

对于复杂业务,按 DDD 的 Bounded Context(限界上下文)划分。

  • 划分:每个模块(如:销售、库存、财务)都是独立的限界上下文。
  • 核心规则
    • 模块之间禁止共享数据库表(物理隔离)。
    • 模块之间禁止直接调用对方的 Repository
    • 只能通过 Domain Events(领域事件)Application Service 进行通信。
    • 数据同步通过事件(如 OrderPlaced 事件触发 Inventory 模块扣减库存)。

单一职责边界:文件与类设计

在同一个模块内部,也要划分清晰的职责边界,避免上帝类。

  • Action 模式:一个类只做一件事。User 模块下拆分为 RegisterUserUpdateUserProfileChangePassword 三个 Action 类,而不是一个 UserService 处理所有。
  • DTO(数据传输对象):跨模块传参时,不要直接传递 Eloquent Model 或 Entity,应传递轻量级 DTO,防止模块 A 的实体修改直接污染模块 B 的逻辑。

实施建议:如何落地?

如果你有一个现有的混乱项目,建议按以下步骤逐步推进:

  1. 第一步:梳理显式依赖 检查 use 语句,找出耦合点,确定哪些模块可以合并,哪些必须拆分。

  2. 第二步:定义接口层 禁止模块 A 直接 new 模块 B 的类,引入 Interface 和依赖注入容器(PSR-11)。

  3. 第三步:建立“防腐层”(Anti-Corruption Layer) 如果无法完全隔离数据,在模块 A 和 B 之间建立一个转换层,负责翻译双方的模型和数据结构。

  4. 第四步:利用工具强制约束

    • Deptrac:这是一个 PHP 静态分析工具,专门用来定义和检查模块依赖规则,你可以在 deptrac.yaml 中声明“Order 模块不允许依赖 Inventory 模块”,并集成到 CI,若违反则构建失败。
    • PHPStan / Psalm:配合 @api 注解或 --level=8 严格检查未定义的依赖。

边界的本质

PHP 的模块边界不仅是代码层面的物理隔离,更是团队之间的口头协议

  • 顶层(目录):决定代码放在哪里。
  • 中间层(Composer + Interface):决定谁允许知道谁。
  • 底层(事件/异步消息):实现最终一致性和物理解耦。

最简单的判断标准:如果修改 User 模块的内部代码,会导致 Order 模块的测试失败,那么这两个模块的边界就不清晰,需要重构。

抱歉,评论功能暂时关闭!