Laravel 5.5 — 分享一个简化控制器业务逻辑的方案
Laravel 的自由度超越了许多前期框架,这才是它真正的力量来源,但是伴随力量而来的是职责。
许多大型应用程序在强制执行业务规则时都会失败- - -我在许多项目中都出现过业务逻辑的错误,不管是在仓库、控制器、事件,还是服务中。
在本文中,我将概述一种更有效的方法,以避免我们在编写业务逻辑的时候经常会犯的错误。
业务逻辑究竟是什么?
业务逻辑可以被描述为我们在我们的应用程序中定义的流程,该流程将执行所需的业务规则。
- 在系统中执行特定操作之前应该考虑什么条件?
- 这个操作需要哪些权限?
- 系统的其他部分如何受到这些操作的影响?
您应该清晰、大胆的地定义业务规则在你开始构建功能需求之前,当你后面需要修改它时这将有助于保持您的合理设计。
问题
想象一下,我们的任务是创建一个应用程序,帮助用户决定他们应该吃哪些食物。来看看我们可能想要通过应用程序执行的一些业务规则。
- "作为一个吃货,我不能吃不健康的食物"。
- "作为一个吃货,当食物的卡路里含量超过我今日规定的剩余卡路里摄入量时,我不能吃这些食物"。
public function eat(Food $food)
{
if (user()->isOnDiet() && $food->isUnhealthy()) {
return response()->json([
'This food is too unhealthy to eat! Say no!',
], 422);
}
$this->eatenFoodsRepository()->save($user, $food);
return response()->json(['message' => 'Yum!']);
}
现在规定每天最多吃 2000 卡路里能量的食物。
public function eat(Food $food)
{
if (user()->countRemainingCalories() - $food->countCalories() > 2000) {
return response()->json([
'This food is too many calories to eat. No',
], 422);
}
if (user()->isOnDiet() && $food->isUnhealthy()) {
return response()->json([
'This food is too unhealthy to eat! Say no!',
], 422);
}
...
}
对于我们添加的每个条件,都在我们的代码中添加一个 IF
代码块。当条件越来越复杂时这将变得很难测试。
事情可能非常容易失控...
让我们整理一下思路
这样编写控制器不是很好吗?
public function eat(Food $food)
{
$this->business([
new FoodIsHealthy(user(), $food),
new UserHasEnoughCaloriesRemaining(user(), $food),
]);
$this->eatenFoodsRepository()->save($user, $food);
return 'Yum!';
}
在这个示例中,你可以很清晰的看到业务对象在控制器的方法中被定义。这对我们后来能够明确地知道哪些业务规则被实施是一个简单有效的方法。
将该逻辑的操作执行委托给另一个类会给我们带来更好的可测试性,这在执行业务逻辑时是至关重要的。
让我们开始吧!
首先我们定义一个业务对象:
class FoodIsHealthy
{
public function __construct($user, $food)
{
$this->user = $user;
$this->food = $food;
}
public function passes()
{
if ($this->user->isOnDiet() && $this->food->isUnhealthy()) {
return false;
}
return true;
}
public function message()
{
return 'This food is too unhealthy to eat! Say no!';
}
}
这个类非常简洁,干净,同时提供优雅的方式来校验检查和强化我们的对象。
我会建议添加
passes()
和message()
方法来为这些对象添加接口契约。
接下来,让我们编写一个方法来处理基类控制器中的所有业务对象:
/**
* 执行业务逻辑
*
* @param array $objectives
* @return void
*
* @throws \Illuminate\Validation\ValidationException
*/
public function business($objectives)
{
$messages = tap(collect(), function($messages) use($objectives) {
collect($objectives)->each(function($objective) use($messages) {
if (! $objective->passes()) {
$messages->put('business', $objective->message());
}
});
});
if (! $messages->isEmpty()) {
throw new ValidationException(
app('validator'), new JsonResponse($messages, 422)
);
}
}
以上我们只是简单的遍历了所有对象,并检查它们是通过还是失败。如果失败,我们将收集错误消息,然后返回这些错误信息。
这与基类控制器的
validate()
方法非常相似。
现在你可以很方便地执行业务规则了!
回顾
应用这种思想并不困难,你可以从今天开始尝试在你的应用中使用他。当你在设计和编写类的时候,尝试着去思考如何简化控制器,并遵循 SOLID 原则 。
希望本文在如何简化代码方面能够给你提供一些帮助!
本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
类名改叫
HealthyFood
比FoodIsHealthy
要好吧这个类定义在那个目录下呢?
@Tango 我觉得应该是 新建 App/Extensions目录,将自己的类放到这下面,我是这么做的,不知道合适不合适, :flushed:
这个方式可以看作是一下迷你版的仓库模式,个人拙见。
还有我可能发现了一处错误
应该是: