本文目录导读:

- 方案一:写在模型(Model)—— 推荐(胖模型,瘦控制器)
- 方案二:写在控制器(Controller)—— 不推荐(瘦模型,胖控制器)
- 方案三:写在服务层(Service Layer)—— 适合大型复杂项目(推荐升级)
- 总结:到底放哪?
- 行业通用准则(经验总结)
在 PHP 开发中,业务逻辑应该放在哪里是一个经典问题,但没有绝对的答案,取决于你的架构模式、项目规模和团队规范。
以下是最主流的三种方案,以及它们的适用场景和优缺点:
写在模型(Model)—— 推荐(胖模型,瘦控制器)
这是 MVC 的经典思想,也是目前 Laravel、Symfony 等主流框架社区大力推崇的做法。
核心原则: 控制器只负责“接收请求”和“返回响应”,所有的业务规则、计算、校验都放在模型类中。
-
代码示例:
// 控制器 class OrderController extends Controller { public function store(Request $request) { // 1. 只做参数接收(或表单请求校验) // 2. 调用模型的方法处理业务 $order = Order::createOrder($request->user(), $request->all()); return response()->json($order); } } // 模型或服务类 class Order extends Model { public static function createOrder(User $user, array $data) { // 业务逻辑:检查库存、计算价格、生成订单号等 if ($data['quantity'] > $product->stock) { throw new \Exception('库存不足'); } // ... 复杂的业务计算 $order = self::create([...]); // 记录日志、发送通知等 return $order; } } -
优点: 控制器非常薄,易于测试;业务逻辑复用性强(API 和 Web 同时调用同一模型方法)。
-
缺点: 模型可能会变得非常庞大(上帝类),如果业务复杂,需要进一步拆分服务层。
写在控制器(Controller)—— 不推荐(瘦模型,胖控制器)
通常出现在初学者代码或快速原型中。
核心原则: 所有逻辑都写在 store()、update() 等方法里。
- 缺点: 控制器充满了
if...else和 SQL 片段,极难测试和维护;业务逻辑无法复用;违反单一职责原则。
写在服务层(Service Layer)—— 适合大型复杂项目(推荐升级)
当业务逻辑非常复杂(涉及多模型交互、外部 API 调用、消息队列)时,把逻辑放在模型里会让模型过于臃肿,此时会引入 Service(服务)层。
核心原则: 模型只负责数据表映射(ORM),控制器调用 服务类,服务类专门承载业务规则。
-
代码示例:
// 1. 服务类 class OrderService { public function checkout(User $user, Cart $cart) { // 聚合操作多个模型 $total = $this->calculatePrice($cart); $order = Order::create([...]); // 这里只写数据操作,不写复杂的业务判断 $this->paymentGateway->charge($user, $total); return $order; } } // 2. 控制器注入服务 class OrderController extends Controller { public function __construct(private OrderService $orderService) {} public function store(Request $request) { $order = $this->orderService->checkout($request->user(), $request->cart); return response()->json($order); } }
到底放哪?
| 场景 | 推荐位置 | 理由 |
|---|---|---|
| CRUD 简单逻辑 (增删改查,薄校验) |
模型(Model) | 直接写在模型静态方法中,简单快捷。 |
| 中等复杂度逻辑 (涉及 1-2 个表的计算) |
模型(Model) | 如 Order::calculateTotal(),保持高内聚。 |
| 复杂业务 (涉及 3+ 个模型、第三方 API、事务) |
服务层(Service) | 分离关注点,避免模型变成“上帝对象”。 |
| 绝不 | 控制器 | 无论何时,都避免在控制器中写 SQL 或复杂 if。 |
行业通用准则(经验总结)
- 瘦控制器,胖模型 是基础,控制器绝对不写业务逻辑,只负责 HTTP 请求的输入输出。
- 业务逻辑与数据访问分离,如果你的
Model里既包含了验证、计算,又包含了对其他表的where查询,且代码超过 500 行,请考虑拆分为 Service + Repository 模式。 - 对于 Laravel 开发者:强烈建议利用 FormRequest(做请求参数校验) + Action/Service 类(做业务逻辑) + Model(做数据持久化),不要把校验逻辑写在控制器里,更不要写在模型中通过
$request->validate()直接调用。
一句话结论: 优先放 模型(Model);如果模型太大,则提升到 服务层(Service);永远不要写在 控制器 里。