繁体   English   中英

具有数据库事务的Laravel控制器模型异常处理结构

[英]Laravel Controller-Model Exception Handling structure with database transactions

关于体系结构,将异常从模型扔到控制器时,哪两种是好的做法?

结构A:

UserController.php

public function updateUserInfo(UserInfoRequest $request, UserModel $userModel)
{
    $isError = false;
    $message = 'Success';

    try {
        $message = $userModel->updateUserInfo($request->only(['username', 'password']));
    } catch (SomeCustomException $e) {
        $isError = true;
        $message = $e->getMessage();
    }

    return json_encode([
        'isError' => $isError,
        'message' => $message
    ]);
}

UserModel.php

public function updateUserInfo($request)
{
    $isError = false;
    $message = 'Success';

    $username = $request['username'];
    $password = $request['password'];

    try {
        $this->connect()->beginTransaction();

        $this->connect()->table('users')->where('username', $username)->update(['password' => $password]);

        $this->connect()->commit();
    } catch (\Exception $e) {
        $this->connect()->rollback();
        $isError = true;
        $message = $e->getMessage();        
    }

    return [
        'isError' => $isError,
        'message' => $message
    ];
}

结构B:

UserController.php

public function updateUserInfo(UserInfoRequest $request, UserModel $userModel)
{
    $isError = false;
    $message = 'Success';

    try {
        $userModel->updateUserInfo($request->only(['username', 'password']));
    } catch (SomeCustomException $e) {
        $isError = true;
        $message = $e->getMessage();
    } catch (QueryException $e) {
        $isError = true;
        $message = $e->getMessage();    
    }

    return json_encode([
        'isError' => $isError,
        'message' => $message
    ]);
}

UserModel.php

public function updateUserInfo($request)
{
    $username = $request['username'];
    $password = $request['password'];

    try {
        $this->connect()->beginTransaction();

        $this->connect()->table('users')->where('username', $username)->update(['password' => $password]);

        $this->connect()->commit();
    } catch (\Exception $e) {
        $this->connect()->rollback();
        throw new QueryException();
    }
}

结构A中 ,模型捕获任何异常,回滚事务,如果有错误或没有错误返回给控制器。 然后,控制器仅返回从模型返回的任何内容。

当模型在结构B中捕获任何异常时,回滚事务,如果发生异常,则抛出QueryException。 然后,控制器从模型中捕获抛出的QueryException,然后捕获返回的错误(如果有错误或没有错误)。

结构B仍然存在问题的原因是模型应该是进行回滚的模型。 如果我要在这里删除模型的try-catch,而控制器直接捕获异常,那么回滚将在控制器上进行,我认为这会使控制器的功能混乱。

让我知道你的想法。 谢谢!

为什么我认为B的方法更好:

  1. 您的模型应仅包括逻辑部分:这包括与数据库的通信(事务和回滚), 而不包括要打印给用户的错误消息的格式。

  2. 保持模型干净:这是MVC结构中最重要的部分。 如果搞砸了,将很难发现任何错误。

  3. 外包错误处理:如果将其放在控制器中,则可以选择在其中进行处理(也许您需要此方法的某些特殊格式的输出,或者需要一些其他函数来调用),或者在App\\Exceptions\\Handler 在这种情况下,您可以在此处呈现此错误消息,而不必在控制器中执行。

因此,如果您不需要任何特殊的函数调用并且想使用Laravel的全部功能,我建议您使用Structure C

UserController.php

public function updateUserInfo(UserInfoRequest $request, UserModel $userModel)
{
    $userModel->updateUserInfo($request->only(['username', 'password']));
    return response()->json(['message' => 'updated user.']); 
}

UserModel.php

public function updateUserInfo($request)
{
    $username = $request['username'];
    $password = $request['password'];
    try {
        $this->connect()->beginTransaction();

        $this->connect()->table('users')->where('username', $username)->update(['password' => $password]);

        $this->connect()->commit();
    } catch (\Exception $e) {
        $this->connect()->rollback();
        throw new QueryException();
    }
}

应用\\异常\\处理程序

public function render($request, Exception $exception)
{
    //catch everything what you want
    if ($exception instanceof CustomException) {
        return response()->json([
          'message' => $exception->getMessage()
        ], 422);
    }

    return parent::render($request, $exception);
}

您将数据库内容(模型),表示内容(控制器)和错误处理(处理程序)完全分开。 结构C允许您在其他控制器功能相同的情况下,在其他功能中重用错误处理。

这是我的观点,但是我愿意讨论任何您认为这种方法不是最佳解决方案的情况。

首先 ,对于您的示例,您甚至不需要使用Transaction。 您仅执行一个查询。 那么为什么需要回滚? 您要回滚哪个查询? 当您需要完全处理一组更改以使操作完整且有效时,应使用事务。 如果第一个成功,但是以下任何一个有任何错误,则可以回滚所有内容,就像什么都没做一样。

其次,让我们指出良好实践或最佳实践。 Laravel建议使用Thin控制器和Thin模型。 因此,您的所有业务逻辑都应该在模型中,甚至应该在存储库中更好。 控制器将充当经纪人。 它将从存储库或模型中收集数据并将其传递给视图。

另外 ,laravel提供了一些不错且方便的方式来组织代码。 您可以将EventObservers用于模型中的并发操作。

最佳实践因用户的知识和经验而异。 谁知道,您问题的最佳答案尚未到来。

我宁愿保留与模型交互的控制器和系统的任何其他部分,尽可能与模型的内部运作方式无关。 因此,例如,我将尝试避免在模型之外意识到QueryException ,而是尽可能将其视为纯PHP对象。

另外,我会避免使用自定义JSON响应结构,而使用HTTP状态 如果有道理,也许更新用户信息的路由返回更新后的资源,或者200 OK就足够了。

// UserModel.php
public function updateUserInfo($request)
{
    $username = $request['username'];
    $password = $request['password'];

    try {
        $this->connect()->beginTransaction();

        $this->connect()->table('users')->where('username', $username)->update(['password' => $password]);

        $this->connect()->commit();

        return $this->connect()->table('users')->where('username', $username)->first();
        // or just return true;
    } catch (\Exception $e) {
        $this->connect()->rollback();

        return false;
    }
}

// UserController.php    
public function updateUserInfo(UserInfoRequest $request, UserModel $userModel)
{
    $updated = $userModel->updateUserInfo($request->only(['username', 'password']));

    if ($updated) {
        return response($updated);
        // HTTP 200 response. Returns JSON of updated user.
        // Alternatively,
        // return response('');
        // (200 OK response, no content)
    } else {
        return response('optional message', 422);
        // 422 or any other status code that makes more sense in the situation.
    }

(完全偏离主题,我想这是一个例子,但以防万一,提醒不要存储纯文本密码。)

我不明白,为什么您不观看Jeffry课,但是对于更新用户,您不需要try / catch部分。 您的控制器方法:

public function update(UpdateUserRequest $request, User $user) : JsonResponse
{
   return response()->json($user->update($request->all()))
}

您请求规则方法:

public function rules(): array
{
    return [
        'username' => 'required|string',
        'password' => 'required|min:6|confirmed',
    ];
}

然后您使用Exception Handler渲染方法:

public function render($request, Exception $exception)
{
    if ($request->ajax() || $request->wantsJson()) {
        $exception = $this->prepareException($exception);

        if ($exception instanceof \Illuminate\Http\Exception\HttpResponseException) {
            return $exception->getResponse();
        } elseif ($exception instanceof \Illuminate\Auth\AuthenticationException) {
            return $this->unauthenticated($request, $exception);
        } elseif ($exception instanceof \Illuminate\Validation\ValidationException) {
            return $this->convertValidationExceptionToResponse($exception, $request);
        }

        // we prepare custom response for other situation such as modelnotfound
        $response = [];
        $response['error'] = $exception->getMessage();

        if (config('app.debug')) {
            $response['trace'] = $exception->getTrace();
            $response['code'] = $exception->getCode();
        }

        // we look for assigned status code if there isn't we assign 500
        $statusCode = method_exists($exception, 'getStatusCode')
            ? $exception->getStatusCode()
            : 500;

        return response()->json($response, $statusCode);
    }
    return parent::render($request, $exception);
}

现在,如果您有异常,Laravel在Json中为您提供状态代码!= 200,否则给出成功结果!

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM