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

翻译 Summer ⋅ 于 2个月前 ⋅ 最后回复由 jikeshow 22小时前 ⋅ 2494 阅读 ⋅ 原文地址

站点的翻译文章创建时,您将第一时间收到通知。

这是一篇社区协同翻译的文章,已完成翻译,更多信息请点击 协同翻译介绍

file

Laravel 的自由度超越了许多前期框架,这才是它真正的力量来源,但是伴随力量而来的是职责。

许多大型应用程序在强制执行业务规则时都会失败- - -我在许多项目中都出现过业务逻辑的错误,不管是在仓库、控制器、事件,还是服务中。

在本文中,我将概述一种更有效的方法,以避免我们在编写业务逻辑的时候经常会犯的错误。

似水流年1013 翻译于 2个月前

业务逻辑究竟是什么?

业务逻辑可以被描述为我们在我们的应用程序中定义的流程,该流程将执行所需的业务规则。

  • 在系统中执行特定操作之前应该考虑什么条件?
  • 这个操作需要哪些权限?
  • 系统的其他部分如何受到这些操作的影响?

您应该清晰、大胆的地定义业务规则在你开始构建功能需求之前,当你后面需要修改它时这将有助于保持您的合理设计。

问题

想象一下,我们的任务是创建一个应用程序,帮助用户决定他们应该吃哪些食物。来看看我们可能想要通过应用程序执行的一些业务规则。

  • "作为一个吃货,我不能吃不健康的食物"。
  • "作为一个吃货,当食物的卡路里含量超过我今日规定的剩余卡路里摄入量时,我不能吃这些食物"。
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 代码块。当条件越来越复杂时这将变得很难测试。

事情可能非常容易失控...

godruoyi 翻译于 2个月前

让我们整理一下思路

这样编写控制器不是很好吗?

public function eat(Food $food)
{
    $this->business([
        new FoodIsHealthy($food),
        new UserHasEnoughCaloriesRemaining(user(), $food),
    ]);
    $this->eatenFoodsRepository()->save($user, $food);
    return 'Yum!';
}

在这个示例中,你可以很清晰的看到业务对象在控制器的方法中被定义。这对我们后来能够明确地知道哪些业务规则被实施是一个简单有效的方法。

将该逻辑的操作执行委托给另一个类会给我们带来更好的可测试性,这在执行业务逻辑时是至关重要的

似水流年1013 翻译于 2个月前

让我们开始吧!

首先我们定义一个业务对象:

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() 方法来为这些对象添加接口契约。

BradStev 翻译于 2个月前

接下来,让我们编写一个方法来处理基类控制器中的所有业务对象:

     /**
     * 执行业务逻辑
     *
     * @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 原则 。

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

似水流年1013 翻译于 2个月前

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

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


本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

本帖已被设为精华帖!
回复数量: 3
  • 类名改叫HealthyFoodFoodIsHealthy要好吧

    1个月前
  • 这个类定义在那个目录下呢?

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

    1个月前
您需要登陆以后才能留下评论!

Composer 中国全量镜像

Top 250 扩展包

Lumen 中文文档

Laravel 速查表

Laravel 中文文档

Laravel 项目开发规范

Laravel 开发环境部署

Composer 中文文档

Elasticsearch-PHP 中文文档

Lumen 中文文档

GraphQL PHP 中文文档

社区文档撰写指南

TDD 构建 Laravel 论坛笔记

PHP PSR 标准规范

PHP 设计模式全集

Dingo API 中文文档