简体   繁体   English

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

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

With regards to architecture, which of the two is a good practice when throwing exceptions from model to controller? 关于体系结构,将异常从模型扔到控制器时,哪两种是好的做法?

Structure A: 结构A:

UserController.php 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 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
    ];
}

Structure B: 结构B:

UserController.php 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 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();
    }
}

In Structure A , the model catches any exception, rollback the transaction and return if it has an error or none to the controller. 结构A中 ,模型捕获任何异常,回滚事务,如果有错误或没有错误返回给控制器。 The controller then just return whatever is returned from the model. 然后,控制器仅返回从模型返回的任何内容。

While in Structure B the model catches any exception, rollback the transaction then throw a QueryException if an exception occurred. 当模型在结构B中捕获任何异常时,回滚事务,如果发生异常,则抛出QueryException。 The controller then catches the thrown QueryException from the model then the return if it has an error or none. 然后,控制器从模型中捕获抛出的QueryException,然后捕获返回的错误(如果有错误或没有错误)。

The reason why Structure B still have a catch is that the model should be the one to do the rollback. 结构B仍然存在问题的原因是模型应该是进行回滚的模型。 If I were to remove the try-catch on the model here and the controller to directly catch an exception, then the rollback will be handled on the controller which I think kind of clutters the functionality of the controller. 如果我要在这里删除模型的try-catch,而控制器直接捕获异常,那么回滚将在控制器上进行,我认为这会使控制器的功能混乱。

Let me know your thoughts. 让我知道你的想法。 Thanks! 谢谢!

Why I think the approach from B is better: 为什么我认为B的方法更好:

  1. Your Model should only include the logical part: This includes the communication with the database (transaction and rollback), not the formatting for the error message you want to print to the user. 您的模型应仅包括逻辑部分:这包括与数据库的通信(事务和回滚), 而不包括要打印给用户的错误消息的格式。

  2. Keep your model clean: It's the most important part of the MVC-structure. 保持模型干净:这是MVC结构中最重要的部分。 If you mess it up it will be very difficult to find any errors. 如果搞砸了,将很难发现任何错误。

  3. Outsourcing the error-handling: if you put it in the controller you have the choice to handle it there (maybe you want some special formatted output for this method or you need some other functions to call) or you handle it in the App\\Exceptions\\Handler . 外包错误处理:如果将其放在控制器中,则可以选择在其中进行处理(也许您需要此方法的某些特殊格式的输出,或者需要一些其他函数来调用),或者在App\\Exceptions\\Handler In this case you can render this error message here and don't have to do it in the controller. 在这种情况下,您可以在此处呈现此错误消息,而不必在控制器中执行。

So if you don't need any special function calls and want to use the full power of Laravel I would suggest you Structure C 因此,如果您不需要任何特殊的函数调用并且想使用Laravel的全部功能,我建议您使用Structure C

UserController.php UserController.php

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

UserModel.php 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();
    }
}

App\\Exceptions\\Handler 应用\\异常\\处理程序

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

You have a clean separation of the Database stuff (Model), the presentation stuff (Controller) and the error handling (Handler). 您将数据库内容(模型),表示内容(控制器)和错误处理(处理程序)完全分开。 Structure C allows you to reuse the error-handling in other functions where you have the same situation in another controller function. 结构C允许您在其他控制器功能相同的情况下,在其他功能中重用错误处理。

This is my opinion but I am open to discuss about any scenario where you think this approach isn't the best solution. 这是我的观点,但是我愿意讨论任何您认为这种方法不是最佳解决方案的情况。

First of all , for your example, you don't even need to use Transaction. 首先 ,对于您的示例,您甚至不需要使用Transaction。 You are performing just one query. 您仅执行一个查询。 so why do you need to rollback? 那么为什么需要回滚? Which query you want to rollback? 您要回滚哪个查询? A transaction should be used when you need a set of changes to be processed completely to consider the operation complete and valid. 当您需要完全处理一组更改以使操作完整且有效时,应使用事务。 If the first one is successful, but any of the following has any error, you can rollback everything as if nothing was ever done. 如果第一个成功,但是以下任何一个有任何错误,则可以回滚所有内容,就像什么都没做一样。

Secondly, Lets come to the point good practice or best practice. 其次,让我们指出良好实践或最佳实践。 Laravel suggest Thin controller and Thick model. Laravel建议使用Thin控制器和Thin模型。 So all of your business logic should be in model or even better in a repository. 因此,您的所有业务逻辑都应该在模型中,甚至应该在存储库中更好。 Controller will be act as a broker. 控制器将充当经纪人。 It will collect data from repository or model and pass it to view. 它将从存储库或模型中收集数据并将其传递给视图。

Alternately , laravel provides some nice and convenient way to organize your codes. 另外 ,laravel提供了一些不错且方便的方式来组织代码。 You can use Event and Observers for concurrent operation in your model. 您可以将EventObservers用于模型中的并发操作。

Best practice is varies based on the knowledge and experience of the users. 最佳实践因用户的知识和经验而异。 So who knows, the best answer for your question is yet to come. 谁知道,您问题的最佳答案尚未到来。

I'd rather keep the controllers and any other part of the system that interacts with the models, as agnostic as possible about the inner workings of the model. 我宁愿保留与模型交互的控制器和系统的任何其他部分,尽可能与模型的内部运作方式无关。 So for instance I'd try to avoid being aware of QueryException s outside of the model and instead treat it as a plain PHP object whenever possible. 因此,例如,我将尝试避免在模型之外意识到QueryException ,而是尽可能将其视为纯PHP对象。

Also I'd avoid the custom JSON response structure and use HTTP statuses . 另外,我会避免使用自定义JSON响应结构,而使用HTTP状态 If it makes sense, maybe the route for updating the user info returns the updated resource, or maybe a 200 OK is enough. 如果有道理,也许更新用户信息的路由返回更新后的资源,或者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.
    }

(Completely off-topic, I guess this is an example, but just in case, a reminder not to store plain text passwords.) (完全偏离主题,我想这是一个例子,但以防万一,提醒不要存储纯文本密码。)

i don't understand, why you not watched Jeffry lesson, but for updating user you don't need try/catch section. 我不明白,为什么您不观看Jeffry课,但是对于更新用户,您不需要try / catch部分。 you controller method: 您的控制器方法:

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

you request rules method: 您请求规则方法:

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

And you Exception Handler render method: 然后您使用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);
}

Now, if you have Exception, Laravel give you in Json with status code != 200, else give success result! 现在,如果您有异常,Laravel在Json中为您提供状态代码!= 200,否则给出成功结果!

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

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