依赖方向规则
清晰的依赖方向是架构稳定性的基础
目录
依赖方向图
┌─────────────────────────────────────────────────────────┐
│ Controller Layer │
│ (HTTP Input/Output) │
└────────────────────────┬────────────────────────────────┘
│ depends on
↓
┌─────────────────────────────────────────────────────────┐
│ Service Layer │
│ (Use Case Orchestration) │
└────────┬────────────────────────────────────────────────┘
│ depends on
↓
┌────────────────────────┐ ┌─────────────────────┐
│ Domain Layer │←───────│ Contract Layer │
│ (Business Logic) │ impl │ (Interfaces) │
└────────────────────────┘ └──────────┬──────────┘
↑ ↑
│ depends on │ implements
│ │
┌────────────────────────────────────────────┴────────────┐
│ Infrastructure Layer │
│ (Repository, Gateway, External Services) │
└──────────────────────────────────────────────────────────┘允许的依赖
Controller → Service
允许:Controller 可以依赖 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
{
$order = $this->createOrderService->handle(
userId: $request->user()->id,
items: $request->post('items'),
shippingAddress: $request->post('shipping_address')
);
return json(['data' => $order]);
}
}Service → Domain + Contract
允许:Service 可以依赖 Domain 实体和 Contract 接口
php
<?php
declare(strict_types=1);
namespace app\service\order;
use app\contract\repository\OrderRepositoryInterface;
use app\contract\gateway\PaymentGatewayInterface;
use app\domain\order\entity\Order;
use support\Db;
final class CreateOrderService
{
public function __construct(
private readonly OrderRepositoryInterface $orderRepository,
private readonly PaymentGatewayInterface $paymentGateway
) {
}
public function handle(int $userId, array $items, array $shippingAddress): Order
{
return Db::transaction(function () use ($userId, $items, $shippingAddress) {
$order = Order::create($userId, $items, $shippingAddress);
$this->orderRepository->save($order);
$this->paymentGateway->createPaymentIntent($order);
return $order;
});
}
}Infrastructure → Contract + Domain
允许:Infrastructure 实现 Contract 接口,可以依赖 Domain 实体
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);
return $model ? $this->toDomain($model) : null;
}
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();
}
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)
);
}
}Domain → Domain (Same Context)
允许:同一限界上下文内的 Domain 对象可以相互依赖
php
<?php
declare(strict_types=1);
namespace app\domain\order\entity;
use app\domain\order\vo\Money;
use app\domain\order\enum\OrderStatus;
final class Order
{
private function __construct(
private readonly int $id,
private Money $totalAmount,
private OrderStatus $status
) {
}
public function calculateTotal(array $items): void
{
$total = array_reduce(
$items,
fn (Money $carry, array $item) => $carry->add(
Money::fromCents($item['price'] * $item['quantity'])
),
Money::zero()
);
$this->totalAmount = $total;
}
}禁止的依赖
Domain → Framework
禁止:Domain 层不能依赖框架类
php
<?php
// 错误示例 - Domain 依赖框架
namespace app\domain\order\entity;
use support\Db; // 不能依赖 Webman 框架
use support\Redis; // 不能依赖框架
final class Order
{
public function save(): void
{
// Domain 不应该直接访问数据库
Db::table('orders')->insert([...]);
}
}php
<?php
// 正确示例 - 通过 Repository 接口
namespace app\domain\order\entity;
final class Order
{
// Domain 只包含业务逻辑,不负责持久化
public function calculateTotal(): void
{
// 纯业务逻辑
}
}
// 持久化由 Service 层调用 Repository 完成
namespace app\service\order;
use app\contract\repository\OrderRepositoryInterface;
final class CreateOrderService
{
public function __construct(
private readonly OrderRepositoryInterface $orderRepository
) {
}
public function handle(): void
{
$order = Order::create(...);
$this->orderRepository->save($order); // 通过接口持久化
}
}Domain → Model
禁止:Domain 层不能依赖 ORM Model
php
<?php
// 错误示例
namespace app\domain\order\entity;
use app\model\eloquent\Order as OrderModel; // 不能依赖 Model
final class Order
{
public function toModel(): OrderModel
{
// Domain 不应该知道 ORM 的存在
return new OrderModel([...]);
}
}php
<?php
// 正确示例 - 由 Repository 负责转换
namespace app\infrastructure\repository\eloquent;
use app\domain\order\entity\Order;
use app\model\eloquent\Order as OrderModel;
final class EloquentOrderRepository
{
public function save(Order $order): void
{
// Repository 负责 Domain 到 Model 的转换
$model = OrderModel::findOrNew($order->id());
$model->user_id = $order->userId();
$model->save();
}
}Domain → Infrastructure
禁止:Domain 层不能依赖 Infrastructure 实现
php
<?php
// 错误示例
namespace app\domain\order\entity;
use app\infrastructure\gateway\payment\StripePaymentGateway;
final class Order
{
public function pay(): void
{
// Domain 不应该依赖具体实现
$gateway = new StripePaymentGateway();
$gateway->charge($this->totalAmount);
}
}php
<?php
// 正确示例 - 通过 Service 层调用
namespace app\service\order;
use app\contract\gateway\PaymentGatewayInterface;
use app\domain\order\entity\Order;
final class PayOrderService
{
public function __construct(
private readonly PaymentGatewayInterface $paymentGateway
) {
}
public function handle(Order $order): void
{
// Service 层调用外部服务
$this->paymentGateway->charge($order->totalAmount());
}
}Controller → Model
禁止:Controller 不能直接访问 Model
php
<?php
// 错误示例
namespace app\controller\api\v1;
use app\model\eloquent\Order;
use support\Request;
final class OrderController
{
public function index(Request $request): array
{
// Controller 不应该直接查询数据库
return Order::where('user_id', $request->user()->id)->get();
}
}php
<?php
// 正确示例 - 通过 Service 层
namespace app\controller\api\v1;
use app\service\order\GetUserOrdersService;
use support\Request;
final class OrderController
{
public function __construct(
private readonly GetUserOrdersService $getUserOrdersService
) {
}
public function index(Request $request): array
{
// 通过 Service 层获取数据
return $this->getUserOrdersService->handle($request->user()->id);
}
}Controller → Infrastructure
禁止:Controller 不能直接依赖 Infrastructure
php
<?php
// 错误示例
namespace app\controller\api\v1;
use app\infrastructure\repository\eloquent\EloquentOrderRepository;
final class OrderController
{
public function __construct(
private readonly EloquentOrderRepository $orderRepository
) {
}
}php
<?php
// 正确示例 - 依赖 Service
namespace app\controller\api\v1;
use app\service\order\CreateOrderService;
final class OrderController
{
public function __construct(
private readonly CreateOrderService $createOrderService
) {
}
}执行策略
1. PHPStan 规则
创建 phpstan.neon 配置文件:
neon
parameters:
level: 8
paths:
- app
# 禁止 Domain 层依赖框架
ignoreErrors:
-
message: '#app\\domain\\.*# cannot depend on #support\\.*#'
path: app/domain
-
message: '#app\\domain\\.*# cannot depend on #app\\model\\.*#'
path: app/domain
-
message: '#app\\domain\\.*# cannot depend on #app\\infrastructure\\.*#'
path: app/domain
# 禁止 Controller 直接依赖 Model
ignoreErrors:
-
message: '#app\\controller\\.*# cannot depend on #app\\model\\.*#'
path: app/controller
-
message: '#app\\controller\\.*# cannot depend on #app\\infrastructure\\.*#'
path: app/controller运行检查:
bash
composer require --dev phpstan/phpstan
vendor/bin/phpstan analyse2. Rector 自动重构
创建 rector.php 配置:
php
<?php
declare(strict_types=1);
use Rector\Config\RectorConfig;
return RectorConfig::configure()
->withPaths([
__DIR__ . '/app',
])
->withRules([
// 自动检测并报告违反依赖规则的代码
]);3. Code Review Checklist
在 Pull Request 时检查:
- [ ] Domain 层是否依赖了框架类?
- [ ] Domain 层是否依赖了 Model?
- [ ] Controller 是否直接访问 Model?
- [ ] Controller 是否包含业务逻辑?
- [ ] Service 层是否正确使用了接口?
- [ ] Infrastructure 是否实现了 Contract 接口?
4. 单元测试验证
Domain 层应该可以独立测试,不需要启动框架:
php
<?php
declare(strict_types=1);
namespace tests\Unit\Domain\Order;
use app\domain\order\entity\Order;
use app\domain\order\values\Money;
test('order calculates total correctly', function () {
$order = Order::create(
userId: 1,
items: [
['price' => 1000, 'quantity' => 2],
['price' => 500, 'quantity' => 1],
],
shippingAddress: []
);
$order->calculateTotal();
expect($order->totalAmount())->toEqual(Money::fromCents(2500));
});代码示例
完整示例:订单创建流程
1. Controller 层
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
{
$validated = $this->validate($request, [
'items' => 'required|array',
'shipping_address' => 'required|array',
]);
$order = $this->createOrderService->handle(
userId: $request->user()->id,
items: $validated['items'],
shippingAddress: $validated['shipping_address']
);
return json(['data' => $order->toArray()]);
}
}2. Service 层
php
<?php
declare(strict_types=1);
namespace app\service\order;
use app\contract\repository\OrderRepositoryInterface;
use app\contract\repository\UserRepositoryInterface;
use app\domain\order\entity\Order;
use support\Db;
final class CreateOrderService
{
public function __construct(
private readonly OrderRepositoryInterface $orderRepository,
private readonly UserRepositoryInterface $userRepository
) {
}
public function handle(int $userId, array $items, array $shippingAddress): Order
{
return Db::transaction(function () use ($userId, $items, $shippingAddress) {
$user = $this->userRepository->findById($userId);
$order = Order::create($userId, $items, $shippingAddress);
$order->calculateTotal();
$order->validateInventory();
$this->orderRepository->save($order);
return $order;
});
}
}3. Domain 层
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\exception\InvalidOrderException;
final class Order
{
private function __construct(
private readonly int $id,
private readonly int $userId,
private array $items,
private Money $totalAmount,
private OrderStatus $status
) {
}
public static function create(int $userId, array $items, array $shippingAddress): self
{
if (empty($items)) {
throw new InvalidOrderException('Order must have at least one item');
}
return new self(
id: 0,
userId: $userId,
items: $items,
totalAmount: Money::zero(),
status: OrderStatus::Pending
);
}
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 validateInventory(): void
{
// 业务规则验证
}
public function id(): int
{
return $this->id;
}
public function userId(): int
{
return $this->userId;
}
public function totalAmount(): Money
{
return $this->totalAmount;
}
public function status(): OrderStatus
{
return $this->status;
}
}4. Contract 层
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 save(Order $order): void;
public function delete(Order $order): void;
}5. 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);
return $model ? $this->toDomain($model) : null;
}
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();
}
public function delete(Order $order): void
{
OrderModel::destroy($order->id());
}
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)
);
}
}依赖注入配置
在 config/container.php 中配置依赖注入:
php
<?php
declare(strict_types=1);
use app\contract\repository\OrderRepositoryInterface;
use app\infrastructure\repository\eloquent\EloquentOrderRepository;
return [
// 绑定接口到实现
OrderRepositoryInterface::class => DI\autowire(EloquentOrderRepository::class),
];最佳实践
DO
- Controller 只依赖 Service
- Service 依赖 Contract 接口,不依赖具体实现
- Domain 保持纯净,不依赖框架
- Infrastructure 实现 Contract 接口
- 使用依赖注入容器管理依赖
DON'T
- 不要在 Domain 层使用框架类
- 不要在 Controller 直接访问 Model
- 不要在 Service 层依赖具体的 Infrastructure 实现
- 不要跨层级调用(如 Controller → Infrastructure)
- 不要循环依赖