Laravel 5.5  — 分享一个简化控制器业务逻辑的方案
76

file

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($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 原则 。

希望本文在如何简化代码方面能够给你提供一些帮助!


Practice makes perfect.

原文地址:https://medium.com/@matthew.erskine/lara...

译文地址:https://laravel-china.org/topics/12733/l...

本帖已被设为精华帖!
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
讨论数量: 4

类名改叫HealthyFoodFoodIsHealthy要好吧

5个月前

这个类定义在那个目录下呢?

5个月前

@Tango 我觉得应该是 新建 App/Extensions目录,将自己的类放到这下面,我是这么做的,不知道合适不合适, :flushed:

5个月前

  • 请注意单词拼写,以及中英文排版,参考此页
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`, 更多语法请见这里 Markdown 语法
  • 支持表情,使用方法请见 Emoji 自动补全来咯,可用的 Emoji 请见 :metal: :point_right: Emoji 列表 :star: :sparkles:
  • 上传图片, 支持拖拽和剪切板黏贴上传, 格式限制 - jpg, png, gif
  • 发布框支持本地存储功能,会在内容变更时保存,「提交」按钮点击时清空
  请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!