【问题标题】:How to using DRY and service layer in Lumen?如何在 Lumen 中使用 DRY 和服务层?
【发布时间】:2019-03-31 17:56:09
【问题描述】:

我正在 Lumen 框架中创建 api,最近我阅读了有关 DRY 和服务层的信息。直到今天,我的代码中都没有使用这些,所有的逻辑都在控制器中。所以我想开始使用它,但我有一些问题。

这是我的控制器 (UsersController.php) 的一部分,因为整个代码太长了。

<?php

namespace App\Http\Controllers;

use App\User;
use Illuminate\Http\Request;

class UsersController extends Controller
{
    private $request;

    public function __construct(Request $request) {
        $this->request = $request;
    }

    public function destroy($id) {
        $user = User::find($id);

        if (!$user) {
            return response()->json([
                'error' => 'User not found'
            ], 404);
        }

        if ($user->role === 'admin') {
            return response()->json([
                'error' => 'You cant edit admin'
            ], 403);
        }

        $user->delete();

        return response()->json([], 204);
    }
}

查看此代码后,我尝试更改两件事。

  1. 获取用户并返回错误可以在 UserService.php 中完成(我在其他方法中也有此代码,所以这就是为什么我认为在服务中使用此方法是个好主意)。但是正如你所看到的,我想在出现错误时返回响应,当我这样做时,我的代码试图在 json 响应上使用 delete 方法,而不是在用户模型上。在我看来抛出异常不好,因为不符合 DRY 原则。知道如何解决吗?

UserService.php

<?php
namespace App\Services;

use App\User;

class UserService
{
    public function getUserById($id)
    {
        $user = User::find($id);

        if (!$user) {
            return response()->json([
                'error' => 'User not found'
            ], 404);
        }

        if ($user->role === 'admin') {
            return response()->json([
                'error' => 'You cant edit admin'
            ], 403);
        }

        return $user;
    }
}

修改了 UsersController.php/destroy

public function destroy($id) {
    $user = $this->userService->getUserById($id);
    $user->delete(); // not working because sometimes it can return json response

    return response()->json([], 204);
}
  1. 我在控制器、中间件等中使用了很多 json 响应,我想通过创建新类来统一它,但我不知道如何正确使用它。我的意思是在 ResponderService.php 中返回 json 响应可能不会在控制器等其他地方停止执行。或者也许我应该将其创建为助手?

ResponderService.php

<?php
namespace App\Services;

class ResponderService
{
    private function base($data, $status_code)
    {
        $data['status_code'] = $status_code;

        return response()->json($data, $status_code);
    }

    public function error($message, $status_code)
    {
        $data['error'] = $message;
        $data['status'] = 'error';
        $this->base($data, $status_code);
    }
}

我也阅读了关于存储库的信息,但我认为这种模式在我的项目中不会有好处。如果您有其他可以在控制器代码中改进的建议,我愿意接受。

【问题讨论】:

  • 为什么异常不能与 DRY 原则“兼容”?
  • @Philipp 我可能是错的,但我认为当我使用异常时,我需要重复尝试/捕获代码。看我的代码:pastebin
  • @hafer 我建议你把这个问题发到codereview.stackexchange.com
  • DRY 是我的一种状态,就像 SOLID 或 ACID 一样,都是精神上的家伙。 (也许不是酸,至少我的意思是酸)。

标签: php laravel dry lumen service-layer


【解决方案1】:

我认为在您的场景中使用例外没有任何问题。

<?php
namespace App\Services;

use App\User;

class UserService
{
    public function getUserById($id)
    {
        $user = User::find($id);

        if (!$user) {
            throw UserNotFoundException('User not found');
        }

        if ($user->role === 'admin') {
            throw EditAdminException("You can't edit admin.");
        }

        return $user;
    }
}

如果您愿意,这些异常是您在app\Exception 中定义的自定义异常。那么getUserById()方法只能返回一个User,否则会出现异常并返回一个JSON响应给客户端。

Laravel 也已经有了处理第一个异常的简单方法。你可以这样做:

<?php
namespace App\Services;

use App\User;

class UserService
{
    public function getUserById($id)
    {
        $user = User::findOrFail($id);

        if ($user->role === 'admin') {
            throw EditAdminException("You can't edit admin.");
        }

        return $user;
    }
}

如果找不到User,Laravel 将处理抛出一个Illuminate\Database\Eloquent\ModelNotFoundException

这样您就不必担心创建一个ResponderService 来处理异常已经可以为您做的事情。

如果您想标准化您的资源响应,您可以利用 Eloquent Resources 作为 API 的转换层:https://laravel.com/docs/5.7/eloquent-resources

最后,如果您发现要从多个位置删除资源并且不想重复响应,您可以将响应放入事件中:https://laravel.com/docs/5.7/eloquent#events

文档显示了处理事件的复杂方式,但我个人只会在您的模型开始感到臃肿时才会这样做。

您可以将其作为创建 Event 和 Observer 类的更简单替代方法:

public static function boot()
{
    parent::boot();

    static::deleted(function ($model) {
        return response()->json([], 204);
    });
}

该方法只适用于您的用户模型。顺便说一句,我发现的方法是查看Eloquent\Model 上的HasEvents 特征。

现在,说了这么多,我实际上会将所有删除逻辑放在您的UserService 中,并将方法从getUserById 重命名为deleteById。替代方案有点奇怪,因为您说如果它是管理员,您不希望能够通过 id 获取用户。

实际上,您要做的是封装删除用户的逻辑,因此只需将其全部移动到服务上的方法中,或者更好的是只需在模型上使用 delete 事件并将所有那里的逻辑。这样,您甚至不必引入服务。

编辑

根据您在下面的评论,我认为您可能误解了如何在 Laravel 中使用异常。

在一个新的 Laravel 项目中,app\Exeptions\Handler 中有一个类,它可以捕获应用程序中所有未处理的异常。该类首先检查 Exception 是否为 ModelNotFoundException,然后返回 json 响应。

否则,它将捕获的异常传递给其父级的render 方法。

因此,基本上,当您想要创建自定义异常时,您只需创建一个扩展 Exception 并实现 handle 方法的类。

这是一个异常类示例:

<?php

namespace App\Exceptions;

use Exception;

class TicketNotPayableException extends Exception
{
    /**
     * Render an exception into an HTTP response.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Exception  $exception
     * @return \Illuminate\Http\Response
     */
    public function render()
    {
        return response()->json([
            'errors' => [
                [
                    'title' => 'Ticket Not Payable Exception',
                    'description' =>
                        'This ticket has already been paid.'
                ],
            ],
            'status' => '409'
        ], 409);
    }
}

现在响应完全可重用,我的代码中不需要一堆 try-catch 块。 Laravel 的异常处理程序会捕获它,并调用 render 方法。

所以,如果我想在服务中封装支付车票的逻辑,我只需要throw App\Exceptions\TicketNotPayableException;,然后我的控制器只需要执行以下操作:$ticketPaymentService-&gt;pay($ticket);,无需尝试-抓住。如果异常被抛出,它会冒泡,被处理程序捕获,并调用 render 方法,该方法将返回适当的 JSON 响应 - 不需要 Responder

【讨论】:

  • 感谢您的回答。我想要响应者服务,因为我在代码中还有其他地方正在使用它,而不仅仅是在 UserController 中,所以我认为使用 $this-&gt;responder-&gt;notFound('User not found') 之类的东西比使用 return response()-&gt;json(['error' =&gt; 'User not found'], 404) 更好。例外很好,但如果我错了,请纠正我。看看我的代码:pastebin。现在我的控制器中只有destroy方法,但将来我还想编辑方法,我需要从destroy方法重复try/catch代码。
  • 我认为你错过了在这种情况下使用异常的意义。您不需要那些 try-catch 块,因为异常会冒泡并由 Laravel 处理,然后当 Laravel Exeptions\Handler 时触发您在自定义 Exceptionrender 方法中定义的任何逻辑抓住它。我会用更好的解释更新我的答案。
  • 现在我明白了,它按我的预期工作,谢谢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-06-14
  • 2011-09-02
  • 2015-02-21
  • 2011-12-25
  • 2012-11-26
  • 2014-02-13
相关资源
最近更新 更多