Laravel: 在哪里抛出 HTTP 异常

Laravel: Where to throw HTTP Exceptions

背景

在 PHP / Laravel MVC 应用程序响应代码和主体通常由抛出的异常决定。如果抛出 HTTP 异常(继承自 Symfony\Component\HttpKernel\Exception\HttpException),则会抛出正确的响应代码(在某些情况下 JSON 响应)。还有其他类型的非 http 相关异常也可以抛出。

问题

应该在哪里抛出HTTP异常?

我应该在控制器中捕获异常并抛出这些异常的 HTTP 版本吗?还是考虑到 99% 的 MVC 框架应用程序都基于 HTTP 请求 >> 响应生命周期,我是否应该在服务 class、存储库或实用程序的任何深处抛出 HTTP 异常?

应该在哪里抛出HTTP异常?

虽然这通常取决于偏好,但框架本身似乎对此采取了固执己见的立场,即您应该将它们扔到任何地方。事实上 Laravel 有一些有用的帮助程序可以更容易地抛出带有相关响应代码的异常:

abort(403, "Exception message"); //Will throw an HTTP exception with code 403
abort_if(true, 400, "Condition failed"); //Will throw a 400 error if the first parameter is true
abort_unless(false, 422, "Condition failed"); //Will throw a 422 error if the first parameter is false

实际例子:

 public function getById($id) { 
      $model = Model::find($id);
      //These are equivalent 
      if ($model == null) {
         abort(404, "$id not found");
      }
      abort_if($model == null, 404, "$id not found");  
      abort_unless($model != null, 404, "$id not found");
 }

这在手册的Error handling section中有所涉及

请注意 abort 确实会引发 HTTP 异常,因此您仍然可以捕获它们并在需要时处理它们。

关于这个问题似乎存在普遍的误解。据我了解,问题是应该在何处抛出 HTTP 异常,但它正在演变为 HTTP 上下文中更通用的异常处理。

首先,如果你有一个 HTTP 异常,意味着只有在 HTTP request/response 循环的上下文中才有意义的异常,那么你应该能够在它发生的地方抛出它而不是抛出它其他目的是在它到达控制器时对其进行转换,这就是 abort 助手要做的事情。

但是,如果您有一个异常(任何类型的异常)在未处理时应使用特定的 http 响应代码进行解释,您可以选择处理该异常:

  1. 使该异常继承自 Symfony HttpException(这可能感觉有点奇怪,因为一个完全正常的异常继承自 class,在 request/response 之外没有任何意义生命周期)。
  2. 实施 render method within your exception 例如:

    class SpecialException extends Exception { 
       public function render() {
            return response()->view('errors.403', [], 403);
       }
    }
    
  3. 在您的 \App\Exceptions\Handler 中有特定的处理行为,例如:

    class Handler {
           // ....
          public function render($request, $exception) {
              if ($exception instanceof SpecialException) {
                  return response()->view('errors.403', [], 403);
              }
              return parent::render()
          }
    }
    

我的回答并非针对 Laravel,因为我觉得使用框架思维实际上违背了您最初的问题。

始终抛出定制的异常,然后在控制器内处理转换。在这种情况下,将其包装在 HttpException 中。这样做有几个很好的理由:

  • 关于将哪个状态代码和消息委托给实现的决定(在本例中是与您的框架的集成)。这意味着您可以将代码放在任何框架中并单独处理其错误。
  • 您决定需要一个 CLI command/worker,现在您在服务中的 HttpException 抛出对您的 CLI 命令没有意义。

本质上想想一个计算器,它会抛出一个DivisionByZeroException。对于控制器,您可以将其包装在 HttpException 400 BAD REQUEST 中并重新抛出。对于 CLI,您的命令可以让异常呈现在屏幕上 Division By Zero无论如何,这个决定不是由您的服务做出的。