Skip to content

目录结构规范

Webman 项目推荐的目录组织方式


目录


完整目录树

project-root/
├─ app/
│  ├─ controller/              # [Webman 默认] HTTP 控制器
│  │  ├─ api/                  # API 控制器
│  │  │  └─ v1/                # API 版本
│  │  └─ web/                  # Web 页面控制器
│  │
│  ├─ model/                   # [Webman 默认] ORM 模型(仅数据映射)
│  │  └─ eloquent/             # Eloquent 模型
│  │
│  ├─ middleware/              # [Webman 默认] 中间件
│  │  ├─ auth/                 # 认证相关
│  │  ├─ cors/                 # CORS 处理
│  │  └─ rate_limit/           # 限流
│  │
│  ├─ process/                 # [Webman 默认] 自定义进程
│  │  ├─ task/                 # 异步任务
│  │  └─ monitor/              # 监控进程
│  │
│  ├─ service/                 # [新增] 应用层服务
│  │  ├─ order/                # 订单相关用例
│  │  │  ├─ CreateOrderService.php
│  │  │  ├─ CancelOrderService.php
│  │  │  └─ RefundOrderService.php
│  │  ├─ user/                 # 用户相关用例
│  │  └─ payment/              # 支付相关用例
│  │
│  ├─ domain/                  # [新增] 领域层
│  │  ├─ order/                # 订单限界上下文
│  │  │  ├─ entity/            # 实体
│  │  │  │  ├─ Order.php
│  │  │  │  └─ OrderItem.php
│  │  │  ├─ enum/              # 枚举(固定选项)
│  │  │  │  ├─ OrderStatus.php
│  │  │  │  └─ PaymentMethod.php
│  │  │  ├─ vo/                # 值对象(需要验证/运算)
│  │  │  │  ├─ Money.php
│  │  │  │  └─ Address.php
│  │  │  ├─ event/             # 领域事件
│  │  │  │  ├─ OrderCreated.php
│  │  │  │  └─ OrderCancelled.php
│  │  │  └─ rule/              # 业务规则
│  │  │     └─ OrderCancellationRule.php
│  │  │
│  │  ├─ user/                 # 用户限界上下文
│  │  │  ├─ entity/
│  │  │  ├─ enum/
│  │  │  ├─ vo/
│  │  │  └─ event/
│  │  │
│  │  └─ shared/               # 共享领域概念
│  │     └─ vo/
│  │        ├─ Email.php
│  │        └─ PhoneNumber.php
│  │
│  ├─ contract/                # [新增] 接口定义
│  │  ├─ repository/           # 仓储接口
│  │  │  ├─ OrderRepositoryInterface.php
│  │  │  └─ UserRepositoryInterface.php
│  │  ├─ gateway/              # 外部服务网关接口
│  │  │  ├─ PaymentGatewayInterface.php
│  │  │  └─ SmsGatewayInterface.php
│  │  └─ service/              # 服务接口
│  │     └─ NotificationServiceInterface.php
│  │
│  ├─ infrastructure/          # [新增] 基础设施层
│  │  ├─ repository/           # 仓储实现
│  │  │  ├─ eloquent/          # Eloquent 实现
│  │  │  │  ├─ EloquentOrderRepository.php
│  │  │  │  └─ EloquentUserRepository.php
│  │  │  └─ redis/             # Redis 实现
│  │  │     └─ RedisSessionRepository.php
│  │  │
│  │  ├─ gateway/              # 外部服务适配器
│  │  │  ├─ payment/
│  │  │  │  ├─ StripePaymentGateway.php
│  │  │  │  └─ AlipayPaymentGateway.php
│  │  │  └─ sms/
│  │  │     └─ TwilioSmsGateway.php
│  │  │
│  │  ├─ persistence/          # 持久化相关
│  │  │  ├─ doctrine/          # Doctrine 配置
│  │  │  └─ migration/         # 数据库迁移
│  │  │
│  │  └─ cache/                # 缓存实现
│  │     └─ RedisCacheAdapter.php
│  │
│  ├─ support/                 # [新增] 通用支持类
│  │  ├─ helper/               # 辅助函数
│  │  │  └─ array_helper.php
│  │  ├─ trait/                # 可复用 Trait
│  │  │  └─ HasTimestamps.php
│  │  └─ exception/            # 自定义异常
│  │     ├─ BusinessException.php
│  │     └─ ValidationException.php
│  │
│  └─ view/                    # [Webman 默认] 视图文件
│     └─ index/

├─ config/                     # [Webman 默认] 配置文件
│  ├─ app.php
│  ├─ database.php
│  ├─ redis.php
│  ├─ container.php            # 依赖注入容器配置
│  └─ plugin/                  # 插件配置

├─ database/                   # 数据库相关
│  ├─ migrations/              # 迁移文件
│  ├─ seeders/                 # 数据填充
│  └─ factories/               # 模型工厂

├─ public/                     # [Webman 默认] 公共资源
│  ├─ index.php                # 入口文件
│  └─ static/                  # 静态资源

├─ runtime/                    # [Webman 默认] 运行时文件
│  ├─ logs/                    # 日志
│  └─ cache/                   # 缓存

├─ storage/                    # 存储目录
│  ├─ uploads/                 # 上传文件
│  └─ temp/                    # 临时文件

├─ tests/                      # 测试目录
│  ├─ Unit/                    # 单元测试
│  │  ├─ Domain/               # 领域层测试
│  │  └─ Service/              # 服务层测试
│  ├─ Feature/                 # 功能测试
│  │  └─ Api/                  # API 测试
│  └─ Pest.php                 # Pest 配置

├─ vendor/                     # Composer 依赖
├─ .env                        # 环境变量
├─ .env.example                # 环境变量示例
├─ composer.json               # Composer 配置
├─ phpstan.neon                # PHPStan 配置
├─ pint.json                   # Pint 配置
├─ rector.php                  # Rector 配置
└─ README.md                   # 项目说明

目录说明

Webman 默认目录

这些目录保持 Webman 框架的默认结构,不要修改

app/controller/

  • 职责:HTTP 请求入口,负责输入验证和输出格式化
  • 原则:薄控制器,不包含业务逻辑
  • 依赖:只依赖 app/service/
php
<?php

declare(strict_types=1);

namespace app\controller\api\v1;

use app\service\order\CreateOrderService;
use support\Request;
use support\Response;

final class OrderController
{
    public function __construct(
        private readonly CreateOrderService $createOrderService
    ) {
    }

    public function create(Request $request): Response
    {
        // 1. 验证输入
        $validated = $this->validate($request, [
            'items' => 'required|array',
            'shipping_address' => 'required|array',
        ]);

        // 2. 调用服务层
        $order = $this->createOrderService->handle(
            userId: $request->user()->id,
            items: $validated['items'],
            shippingAddress: $validated['shipping_address']
        );

        // 3. 返回响应
        return json([
            'success' => true,
            'data' => $order->toArray(),
        ]);
    }
}

app/model/

  • 职责:ORM 模型,仅用于数据映射
  • 原则:不包含业务逻辑,只有数据访问
  • 使用场景:被 infrastructure/repository/ 使用
php
<?php

declare(strict_types=1);

namespace app\model\eloquent;

use Illuminate\Database\Eloquent\Model;

final class Order extends Model
{
    protected $table = 'orders';

    protected $fillable = [
        'user_id',
        'total_amount',
        'status',
        'shipping_address',
    ];

    protected $casts = [
        'shipping_address' => 'array',
        'total_amount' => 'decimal:2',
    ];
}

app/middleware/

  • 职责:请求/响应处理管道
  • 分类:认证、授权、CORS、限流、日志等

app/process/

  • 职责:自定义进程(队列、定时任务、监控等)
  • 特点:Webman 独有的进程管理能力

新增目录

app/service/ - 应用层

职责

  • 用例编排(Use Case Orchestration)
  • 事务管理
  • 调用领域层和基础设施层
  • 不包含业务规则(规则在 domain 层)

命名规范

  • 文件名:{动词}{名词}Service.php
  • 示例:CreateOrderService.php, CancelOrderService.php

代码示例

php
<?php

declare(strict_types=1);

namespace app\service\order;

use app\contract\repository\OrderRepositoryInterface;
use app\contract\repository\UserRepositoryInterface;
use app\contract\gateway\PaymentGatewayInterface;
use app\domain\order\entity\Order;
use app\domain\order\vo\Money;
use app\domain\order\enum\OrderStatus;
use support\Db;

final class CreateOrderService
{
    public function __construct(
        private readonly OrderRepositoryInterface $orderRepository,
        private readonly UserRepositoryInterface $userRepository,
        private readonly PaymentGatewayInterface $paymentGateway
    ) {
    }

    public function handle(int $userId, array $items, array $shippingAddress): Order
    {
        // 开启事务
        return Db::transaction(function () use ($userId, $items, $shippingAddress) {
            // 1. 获取用户(调用仓储)
            $user = $this->userRepository->findById($userId);

            // 2. 创建订单实体(调用领域层)
            $order = Order::create(
                userId: $user->id(),
                items: $items,
                shippingAddress: $shippingAddress
            );

            // 3. 应用业务规则(领域层负责)
            $order->calculateTotal();
            $order->validateInventory();

            // 4. 持久化(调用仓储)
            $this->orderRepository->save($order);

            // 5. 调用外部服务(支付网关)
            $this->paymentGateway->createPaymentIntent($order);

            return $order;
        });
    }
}

app/domain/ - 领域层

职责

  • 核心业务逻辑
  • 业务规则验证
  • 领域事件
  • 不依赖框架、不直接访问数据库

子目录结构

  • entity/ - 实体(有唯一标识)
  • enum/ - 枚举(固定选项集合)
  • vo/ - 值对象(需要验证/运算)
  • event/ - 领域事件
  • rule/ - 业务规则
  • exception/ - 领域异常

enum/ - 固定选项集合,如状态、类型、方法 vo/ - 需要验证、运算或多属性的值对象

代码示例

php
<?php

declare(strict_types=1);

namespace app\domain\order\entity;

use app\domain\order\vo\Money;
use app\domain\order\enum\OrderStatus;
use app\domain\order\event\OrderCreated;
use app\domain\order\exception\InvalidOrderException;

final class Order
{
    private array $domainEvents = [];

    private function __construct(
        private readonly int $id,
        private readonly int $userId,
        private array $items,
        private Money $totalAmount,
        private OrderStatus $status,
        private readonly \DateTimeImmutable $createdAt
    ) {
    }

    public static function create(int $userId, array $items, array $shippingAddress): self
    {
        // 业务规则验证
        if (empty($items)) {
            throw new InvalidOrderException('Order must have at least one item');
        }

        $order = new self(
            id: 0, // 由仓储层分配
            userId: $userId,
            items: $items,
            totalAmount: Money::zero(),
            status: OrderStatus::Pending,
            createdAt: new \DateTimeImmutable()
        );

        // 触发领域事件
        $order->recordEvent(new OrderCreated($order));

        return $order;
    }

    public function calculateTotal(): void
    {
        $total = array_reduce(
            $this->items,
            fn (Money $carry, array $item) => $carry->add(
                Money::fromCents($item['price'] * $item['quantity'])
            ),
            Money::zero()
        );

        $this->totalAmount = $total;
    }

    public function cancel(): void
    {
        // 业务规则:只有待支付和已支付的订单可以取消
        if (!$this->status->canBeCancelled()) {
            throw new InvalidOrderException('Order cannot be cancelled in current status');
        }

        $this->status = OrderStatus::Cancelled;
        $this->recordEvent(new OrderCancelled($this));
    }

    // Getters
    public function id(): int
    {
        return $this->id;
    }

    public function totalAmount(): Money
    {
        return $this->totalAmount;
    }

    public function status(): OrderStatus
    {
        return $this->status;
    }

    private function recordEvent(object $event): void
    {
        $this->domainEvents[] = $event;
    }

    public function releaseEvents(): array
    {
        $events = $this->domainEvents;
        $this->domainEvents = [];
        return $events;
    }
}

Enum 示例(OrderStatus)

php
<?php

declare(strict_types=1);

namespace app\domain\order\enum;

enum OrderStatus: string
{
    case Pending = 'pending';
    case Paid = 'paid';
    case Shipped = 'shipped';
    case Cancelled = 'cancelled';

    public function isPending(): bool
    {
        return $this === self::Pending;
    }

    public function canBeCancelled(): bool
    {
        return in_array($this, [self::Pending, self::Paid]);
    }

    public function canBeShipped(): bool
    {
        return $this === self::Paid;
    }
}

Value Object 示例(Money)

php
<?php

declare(strict_types=1);

namespace app\domain\order\vo;

final class Money
{
    private function __construct(
        private readonly int $cents
    ) {
        if ($cents < 0) {
            throw new \InvalidArgumentException('Money cannot be negative');
        }
    }

    public static function fromCents(int $cents): self
    {
        return new self($cents);
    }

    public static function fromDollars(float $dollars): self
    {
        return new self((int) round($dollars * 100));
    }

    public static function zero(): self
    {
        return new self(0);
    }

    public function add(self $other): self
    {
        return new self($this->cents + $other->cents);
    }

    public function subtract(self $other): self
    {
        return new self($this->cents - $other->cents);
    }

    public function multiply(int $factor): self
    {
        return new self($this->cents * $factor);
    }

    public function toCents(): int
    {
        return $this->cents;
    }

    public function toDollars(): float
    {
        return $this->cents / 100;
    }

    public function equals(self $other): bool
    {
        return $this->cents === $other->cents;
    }
}

app/contract/ - 接口定义

职责

  • 定义仓储接口
  • 定义外部服务网关接口
  • 定义服务契约

命名规范

  • 接口名:{名词}Interface
  • 示例:OrderRepositoryInterface, PaymentGatewayInterface

代码示例

php
<?php

declare(strict_types=1);

namespace app\contract\repository;

use app\domain\order\entity\Order;

interface OrderRepositoryInterface
{
    public function findById(int $id): ?Order;

    public function findByUserId(int $userId): array;

    public function save(Order $order): void;

    public function delete(Order $order): void;

    public function nextIdentity(): int;
}

app/infrastructure/ - 基础设施层

职责

  • 实现仓储接口
  • 实现外部服务适配器
  • 数据库访问
  • 缓存、消息队列等

代码示例

php
<?php

declare(strict_types=1);

namespace app\infrastructure\repository\eloquent;

use app\contract\repository\OrderRepositoryInterface;
use app\domain\order\entity\Order;
use app\domain\order\vo\Money;
use app\domain\order\enum\OrderStatus;
use app\model\eloquent\Order as OrderModel;

final class EloquentOrderRepository implements OrderRepositoryInterface
{
    public function findById(int $id): ?Order
    {
        $model = OrderModel::find($id);

        if ($model === null) {
            return null;
        }

        return $this->toDomain($model);
    }

    public function save(Order $order): void
    {
        $model = OrderModel::findOrNew($order->id());
        $model->user_id = $order->userId();
        $model->total_amount = $order->totalAmount()->toDollars();
        $model->status = $order->status()->value;
        $model->save();

        // 触发领域事件
        foreach ($order->releaseEvents() as $event) {
            event($event);
        }
    }

    private function toDomain(OrderModel $model): Order
    {
        // 从数据库模型重建领域实体
        return Order::reconstitute(
            id: $model->id,
            userId: $model->user_id,
            totalAmount: Money::fromDollars($model->total_amount),
            status: OrderStatus::from($model->status),
            createdAt: new \DateTimeImmutable($model->created_at)
        );
    }

    public function nextIdentity(): int
    {
        return OrderModel::max('id') + 1;
    }
}

app/support/ - 通用支持

职责

  • 辅助函数
  • 可复用 Trait
  • 自定义异常
  • 不要当垃圾桶:只放真正通用的代码

命名规则

目录命名

  • 全部小写app/domain/order/entity/
  • 使用下划线分隔domain_event/
  • 不使用驼峰domainEvent/ vs domain_event/

命名空间

  • 跟随目录结构app\domain\order\entity
  • 全部小写:保持与目录一致
  • 类名 StudlyCaseOrder.php, OrderStatus.php

文件命名

  • 类文件Order.php (StudlyCase)
  • 接口文件OrderRepositoryInterface.php
  • 配置文件database.php (snake_case)
  • 辅助函数array_helper.php (snake_case)

扩展性考虑

插件化

Webman 支持插件系统,未来可以将模块迁移到 plugin/ 目录:

plugin/
└─ vendor/package/
   ├─ app/
   │  ├─ controller/
   │  ├─ service/
   │  └─ domain/
   └─ config/

微服务拆分

当需要拆分微服务时,按限界上下文拆分:

order-service/
├─ app/
│  ├─ domain/order/
│  ├─ service/order/
│  └─ infrastructure/

user-service/
├─ app/
│  ├─ domain/user/
│  ├─ service/user/
│  └─ infrastructure/

最佳实践

DO

  1. 保持 Webman 默认目录不变
  2. 目录全小写,类名 StudlyCase
  3. 按限界上下文组织 domain 目录
  4. 接口放 contract,实现放 infrastructure
  5. service 层只编排,不写业务规则

DON'T

  1. 不要在 controller 里写业务逻辑
  2. 不要在 domain 里依赖框架类
  3. 不要在 model 里写业务规则
  4. 不要把 support 当垃圾桶
  5. 不要混用大小写目录名

相关文档