Php Laravel:在哪里抛出HTTP异常 背景

Php Laravel:在哪里抛出HTTP异常 背景,php,laravel,http,exception,coding-style,Php,Laravel,Http,Exception,Coding Style,在PHP/Laravel MVC应用程序中,响应代码和主体通常由抛出的异常决定。如果引发HTTP异常(继承自Symfony\Component\HttpKernel\exception\HttpException),则会引发正确的响应代码(在某些情况下还会引发JSON响应)。还有其他类型的与http无关的异常也可以抛出 问题: 应该在哪里抛出HTTP异常 A仅控制器 B任何地方。应用程序堆栈中的深度或深度 我应该在控制器中捕获我的异常并抛出这些异常的HTTP版本吗?或者,考虑到99%的MVC

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

问题:
应该在哪里抛出HTTP异常

  • A仅控制器
  • B任何地方。应用程序堆栈中的深度或深度

我应该在控制器中捕获我的异常并抛出这些异常的HTTP版本吗?或者,考虑到99%的MVC框架应用程序都基于HTTP请求>>响应生命周期,我应该在服务类、存储库或实用程序的任何深处抛出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");
 }
这一点在本手册的第二部分中有所涉及

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

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

首先,如果您有一个HTTP异常,这意味着只有在HTTP请求/响应周期的上下文中才有意义的异常,那么您应该能够在它发生的地方抛出它,而不是为了在它到达控制器时转换它而抛出其他异常,这就是
abort
助手要做的事情

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

  • 使该异常继承自Symfony
    HttpException
    (如果一个完全正常的异常继承自一个在请求/响应生命周期之外没有意义的类,这可能会让人觉得有点奇怪)
  • 实施以下措施,例如:

  • 在\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命令/worker,现在服务中抛出的
      HttpException
      对您的CLI命令没有任何意义

    本质上考虑计算器,它会抛出一个
    DivisionByZeroException
    。对于控制器,您可以将其包装在
    HttpException
    400错误请求中,然后重新抛出。对于CLI,您的命令可以让异常在屏幕上呈现为除零无论哪种方式,此决定都不是由您的服务部门做出的。

    我为提及文档提供了一些支持,但就个人而言,使用特定于框架的函数,如
    中止
    与抛出
    HttpException
    是一样的,因此没有明确回答最初的问题。事实上,在我看来,使用框架
    abort
    功能会被认为更糟糕。更多的理由,请看我的答案。@MatthewUsurp
    abort
    的存在与否是问题的答案。之所以有这种方法,是因为Laravel希望鼓励人们在应用程序中的任何地方抛出HTTP异常。如果你不同意这一点,那就不同于说这不是一个有效的答案。我不同意这个答案,因为它似乎只是重复了Laravel实现(Taylor的),并不总是最佳实践或事件是一个好的方法。@AndrewMcLagan我个人认为这是最佳实践。如果您遇到需要抛出HTTP异常的情况,那么您应该能够从任何地方抛出它。在控制器中放置一个
    try…catch
    ,只是为了将一个异常转换为另一个异常,这太麻烦了。@apokryfos不。在堆栈中的任何位置抛出HTTP异常都会将该代码与HTTP环境中的使用相耦合。如果您以后想在控制台中使用相同的代码并抛出HTTP异常,该怎么办?回答得很好,理由充分。我同样觉得似乎无法理解为什么,你已经很雄辩地解释了为什么
    class Handler {
           // ....
          public function render($request, $exception) {
              if ($exception instanceof SpecialException) {
                  return response()->view('errors.403', [], 403);
              }
              return parent::render()
          }
    }