PHP 服务层和逻辑层

wen PHP项目 2

本文目录导读:

PHP 服务层和逻辑层

  1. 目录导读
  2. 为什么你的PHP代码越写越乱?—— 从MVC失控说起
  3. 核心概念拆解:什么是服务层?什么是逻辑层?
  4. 服务层与逻辑层的职责边界:谁该管数据库?谁该管计算?
  5. 实战案例:从Laravel控制器臃肿到分层重构的完整过程
  6. 进阶问答:关于事务、依赖注入与性能的五大灵魂拷问
  7. 总结:分层不是银弹,但不懂分层连银弹的枪栓都摸不到

PHP架构演进:服务层与逻辑层的解耦艺术,如何让代码告别“屎山”?

目录导读

  1. 为什么你的PHP代码越写越乱?—— 从MVC失控说起
  2. 核心概念拆解:什么是服务层(Service Layer)?什么是逻辑层(Domain/Logic Layer)?
  3. 服务层与逻辑层的职责边界:谁该管数据库?谁该管计算?
  4. 实战案例:从Laravel控制器臃肿到分层重构的完整过程
  5. 进阶问答:关于事务、依赖注入与性能的五大灵魂拷问
  6. 分层不是银弹,但不懂分层连银弹的枪栓都摸不到

为什么你的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年后依旧敢于改动。重构一次,受益三年。

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