【问题标题】:Laravel Controller-Model Exception Handling structure with database transactions带有数据库事务的 Laravel Controller-Model 异常处理结构
【发布时间】:2017-10-07 09:15:07
【问题描述】:

关于架构,当从模型向控制器抛出异常时,两者中哪一个是好的做法?

结构 A:

用户控制器.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:

用户控制器.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 和控制器以直接捕获异常,那么回滚将在控制器上处理,我认为这会扰乱控制器的功能。

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

【问题讨论】:

    标签: php laravel exception-handling transactions try-catch


    【解决方案1】:

    我不明白,你为什么不看 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',
        ];
    }
    

    还有你的异常处理程序渲染方法:

    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,否则返回成功结果!

    【讨论】:

    • 我将事务用于依赖于其他查询的查询,例如插入数据,然后获取新插入数据的 id 以用于在另一个表上插入另一个数据等......
    【解决方案2】:

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

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

    或者,laravel 提供了一些不错且方便的方式来组织您的代码。您可以在模型中使用EventObservers 进行并发操作。

    最佳做法因用户的知识和经验而异。那么谁知道呢,您的问题的最佳答案尚未到来。

    【讨论】:

      【解决方案3】:

      我宁愿保留控制器和系统中与模型交互的任何其他部分,尽可能不了解模型的内部运作。因此,例如,我会尽量避免意识到模型之外的QueryExceptions,而是尽可能将其视为普通的 PHP 对象。

      另外我会避免使用自定义 JSON 响应结构并使用 HTTP statuses。如果有意义的话,更新用户信息的路由可能会返回更新后的资源,或者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.
          }
      

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

      【讨论】:

        【解决方案4】:

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

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

        2. 保持模型整洁:它是 MVC 结构中最重要的部分。如果你把它搞砸了,将很难找到任何错误。

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

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

        用户控制器.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 允许您在另一个控制器函数中具有相同情况的其他函数中重用错误处理。

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

        【讨论】:

        • 我第二次 mimo 的结构 C 建议和标记,不要在您的 json 响应中添加“isError”。 Http 已经提供了一种内置方法来确定响应是否为错误。请参阅 HTTP 状态代码。简而言之 HTTP 状态码: 1xx:等待; 2xx:给你; 3xx:走开; 4xx:你搞砸了; 5xx:我搞砸了
        • @JeremyGiberson,我刚刚添加了与作者相同的 json 响应,但你是对的。最好的方法是设置错误代码,例如422,所以你可以处理它(例如用then()catch()在JS中的承诺)
        猜你喜欢
        • 2013-07-10
        • 2017-01-30
        • 2021-07-21
        • 2013-04-15
        • 1970-01-01
        • 2011-03-21
        • 2021-06-12
        • 2014-04-22
        相关资源
        最近更新 更多