php - 如何在 Lumen 中使用 DRY 和服务层?

标签 php laravel dry lumen service-layer

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

这是我的 Controller (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. 我在 Controller 、中间件等中使用了太多的 json 响应,我想通过创建新类来统一它,但我不知道如何正确使用它。我的意思是在 ResponderService.php 中返回 json 响应可能不会在 Controller 等其他地方停止执行。或者也许我应该将其创建为助手?

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);
    }
}

我也读过有关存储库的内容,但我认为这种模式在我的项目中并不适用。如果您有其他可以改进 Controller 代码的建议,我愿意接受。

最佳答案

我没有发现在您的场景中使用异常有任何问题。

<?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 trait。

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

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

编辑

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

在一个新的 Laravel 项目中,app\Exeptions\Handler 中有一个类可以捕获应用中所有未处理的异常。该类首先检查异常是否为 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 block 。 Laravel 的异常处理程序将捕获它,并调用 render 方法。

因此,如果我想在服务中封装支付票证的逻辑,我只需要throw App\Exceptions\TicketNotPayableException; 然后我的 Controller 只需要做类似的事情: $ticketPaymentService->pay($ticket); 并且不需要 try-catch。如果异常被抛出,它会冒泡,被处理程序捕获,并且 render 方法将被调用,这将返回适当的 JSON 响应 - 不需要 Responder.

关于php - 如何在 Lumen 中使用 DRY 和服务层?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53017757/

相关文章:

php - 如何制作脚本以从JSON API捕获某些数据?

php - 在 Laravel 中获取数据时获取重复行

Laravel - Route::resource 与 Route::controller

html - 复制 Youtube 的导航栏,但是,我习惯使用位置 : absolute; I need a more effective way of doing this

php - 获取通过 POST 发送的所有变量?

php - 如何将 unicode 代码点转换为十六进制 HTML 实体?

python - 有什么办法可以让这台发电机干涸吗?

repository - 命令中的 CQRS 代码重复

javascript - html动态表

php - 如何使用 Controller 为 laravel 数据库中的特定列创建随机字符串值?