本文目录导读:

- 目录导读
- 为什么你的PHP代码越写越乱?—— 从MVC失控说起
- 核心概念拆解:什么是服务层?什么是逻辑层?
- 服务层与逻辑层的职责边界:谁该管数据库?谁该管计算?
- 实战案例:从Laravel控制器臃肿到分层重构的完整过程
- 进阶问答:关于事务、依赖注入与性能的五大灵魂拷问
- 总结:分层不是银弹,但不懂分层连银弹的枪栓都摸不到
PHP架构演进:服务层与逻辑层的解耦艺术,如何让代码告别“屎山”?
目录导读
- 为什么你的PHP代码越写越乱?—— 从MVC失控说起
- 核心概念拆解:什么是服务层(Service Layer)?什么是逻辑层(Domain/Logic Layer)?
- 服务层与逻辑层的职责边界:谁该管数据库?谁该管计算?
- 实战案例:从Laravel控制器臃肿到分层重构的完整过程
- 进阶问答:关于事务、依赖注入与性能的五大灵魂拷问
- 分层不是银弹,但不懂分层连银弹的枪栓都摸不到
为什么你的PHP代码越写越乱?—— 从MVC失控说起
大多数PHPer的起点都是Laravel或ThinkPHP的MVC,初期,控制器(Controller)里写查询、模型(Model)里塞业务逻辑,一切都很“快”,但项目迭代半年后,你会发现:一个OrderController@store方法膨胀到300行,里面既有DB::transaction,又有Mail::send,还夹杂着Cache::put,这被称为“胖控制器、瘦模型”综合征。
根本原因在于:MVC中的“Model”在多数框架里被降级为“数据库表映射”(ORM),而不是“业务领域模型”,真正的业务规则(如:订单总价计算、库存扣减策略、支付状态机流转)无处安放,只能堆在控制器里,服务层与逻辑层的引入,就成了重构的必然选择。
核心概念拆解:什么是服务层?什么是逻辑层?
在PHP现代架构中,我们通常将代码分为四层:表现层(Controller/Action) → 服务层(Service) → 逻辑层(Domain/Repository) → 基础设施层(Model/ORM)。
-
服务层(Service Layer):位于控制器之下,是应用用例的编排者,它接收控制器传递的请求DTO(数据传输对象),负责调用逻辑层的多个方法,协调事务边界,并通过接口返回结果,它不关心SQL怎么写,不关心缓存键名,只关注“完成这个业务动作需要哪些步骤”。
-
逻辑层(Domain Logic / Business Service):这是业务规则的家,它承载了:价格计算、状态校验、库存断言、折扣策略等纯PHP逻辑,它不依赖框架的请求/响应对象,也不直接操作
Model::query(),逻辑层的方法参数是“领域对象”或原始值,返回的是业务结果。calculateFinalPrice(Product $product, User $user, string $couponCode): Money。
关键区别:服务层是可复用的“用例脚本”,而逻辑层是“无状态的规则引擎”,服务层知道“谁调用了它”(前端API、CLI、队列任务),逻辑层不知道“被谁调用”,它只对输入负责。
服务层与逻辑层的职责边界:谁该管数据库?谁该管计算?
这是最容易被混淆的点,参考业界经典(Martin Fowler《企业应用架构模式》),我的划分原则如下:
| 操作类型 | 归属层 | 原因 |
|---|---|---|
| HTTP请求参数解析、权限预检 | 控制器 | 表现层职责 |
| 多步用例编排(A+B+C) | 服务层 | 事务脚本模式(Transaction Script) |
| 复杂业务规则(折扣、税费叠加) | 逻辑层 | 领域模型模式(Domain Model) |
| 数据库查询/写入 | 仓储(Repository)或数据映射器(避开Model) | 基础设施解耦 |
| 发送邮件/外呼API | 服务层(通过接口调用基础设施) | 副作用集中管理 |
一个反例:如果你在服务层写了if ($order->status === pending) { $order->status = paid; },但同时又直接Order::where(...)->update(...),这就破坏了封装,正确做法是:逻辑层提供orderService->markAsPaid($order),内部判断状态机并调用$order->updateStatus()(注意这是领域对象方法)。
实战案例:从Laravel控制器臃肿到分层重构的完整过程
重构前(Controller乱象):
// 臭名昭著的 OrderController@store
public function store(Request $request) {
DB::beginTransaction();
$order = new Order();
$order->user_id = $request->user()->id;
$total = 0;
foreach ($request->items as $item) {
$product = Product::find($item['product_id']);
if ($product->stock < $item['qty']) {
DB::rollBack();
return response()->json(['error' => '库存不足']);
}
$subTotal = $product->price * $item['qty'] * (1 - $product->discount);
$total += $subTotal;
OrderItem::create([...]);
$product->decrement('stock', $item['qty']);
}
$order->total = $total + $request->shipping_fee;
$order->save();
DB::commit();
Mail::to($user)->send(new OrderShipped($order));
}
重构后:
// 1. 服务层:编排用例
class PlaceOrderService {
public function __construct(private OrderCreatorService $creator, private OrderNotifier $notifier) {}
public function execute(array $requestData, User $user): Order {
return DB::transaction(function () use ($requestData, $user) {
$order = $this->creator->createOrder($requestData, $user);
$this->notifier->sendConfirmation($order); // 邮件发送在事务外可考虑,但简例中保留
return $order;
});
}
}
// 2. 逻辑层:纯业务规则
class OrderCreatorService {
public function createOrder(array $items, User $user): Order {
$order = new Order(['user_id' => $user->id]);
$total = 0;
foreach ($items as $itemDto) {
$product = $this->productFinder->find($itemDto->productId);
$this->assertStock($product, $itemDto->quantity); // 抛异常则回滚
$money = new Money($product->price);
$discounted = $this->priceCalculator->applyDiscount($money, $product, $user);
$total = $total->add($discounted->multiply($itemDto->quantity));
$this->stockManager->decrement($product, $itemDto->quantity);
}
$order->total = $total->add($this->shippingFeeCalculator->calculate($items));
$order->save();
return $order;
}
}
重构后,控制器只有两行:$order = app(PlaceOrderService::class)->execute($request->all(), $request->user()); return response()->json($order);
进阶问答:关于事务、依赖注入与性能的五大灵魂拷问
Q1:所有方法都要走服务层吗?CRUD是否可以直接用Model? A:简单的字段增删改(无规则)可直接用仓储(Repository),一旦涉及“多步骤”“状态变化”“外部副作用”,必须走服务层,逻辑层服务于复杂规则,CRUD不属于逻辑层。
Q2:逻辑层里能用DB::transaction()吗?
A:不能,事务是“用例级”的横切关注点,应放在服务层(或中间件/切面),逻辑层强制自己只能操作领域对象的内存状态,让测试变得极其简单(无需数据库即可测试折扣计算)。
Q3:服务层和逻辑层都会变胖,如何避免?
A:引入命令对象(Command Bus) 或策略模式(Strategy)。PlaceOrderCommand 作为DTO,PlaceOrderHandler 作为服务层执行器,逻辑层内部若规则分支过多,拆分为DiscountStrategy接口 + 多个实现类。
Q4:分层会不会拖慢性能?(N+1查询、多次调用)
A:不会,分层只是代码组织方式,物理上的数据库查询次数不变,关键是在服务层使用聚合根(Aggregate Root) 模式,一次装载订单及其子项,而不是在循环中查询,用with()预加载即可,分层不关心SQL优化。
Q5:Laravel的Model自带事件(Observes)能不能替代逻辑层?
A:Observes适合持久化触发的“幽灵逻辑”,如自动填充时间戳,但核心业务规则(如余额计算)不能用Observes,因为无法测试,且易隐式触发,逻辑层的核心优势是可测试性——你可以在不启动框架的情况下直接new OrderCalculator()并断言结果。
分层不是银弹,但不懂分层连银弹的枪栓都摸不到
服务层与逻辑层的核心价值在于分离变与不变:服务层变(接口变更、编排顺序调整),逻辑层不变(规则稳定),对于PHP项目,建议从Laravel或Symfony项目开始,将超过50行的控制器方法抽出第一步——先创建app/Services目录,把事务脚本移进去;第二步识别“纯计算”逻辑,移入app/Domain,即使你不使用DDD,这种分层也能让你的代码在3年后依旧敢于改动。重构一次,受益三年。