>Background
在 PHP/Laravel MVC 应用程序中,响应代码和正文通常由抛出的异常决定。如果抛出 HTTP 异常(继承自SymfonyComponentHttpKernelExceptionHttpException
(,则会抛出正确的响应代码(在某些情况下会抛出 JSON 响应(。还可以引发其他类型的非 http 相关异常。
问题
应该在哪里抛出 HTTP 异常?
- A仅控制器
- B任何地方。应用程序堆栈中的深层或浅层。
我应该在控制器中捕获异常并抛出这些异常的 HTTP 版本吗?或者我应该在服务类、存储库或实用程序的深处抛出 HTTP 异常,考虑到 99% 的 MVC 框架应用程序无论如何都基于 HTTP 请求>>响应生命周期?
我的回答不是针对Laravel的,因为我觉得以框架思维方式工作实际上违背了你最初的问题。
始终引发定制的异常,然后在控制器中处理转换。在这种情况下,请将其包裹在HttpException
中。这有几个很好的理由:
- 决定将哪些状态代码和消息委托给实现(在本例中为与框架集成(。这意味着您可以将代码放在任何框架中并单独处理其错误。
- 您决定需要一个 CLI 命令/工作线程,现在您的服务抛出的
HttpException
对您的 CLI 命令毫无意义。
本质上考虑计算器,它会抛出DivisionByZeroException
.对于控制器,您可以将其包装在HttpException
400 BAD REQUEST
中并重新抛出。对于 CLI,您的命令可以让异常呈现在屏幕上Division By Zero
.无论哪种方式,此决定都不是由你的服务做出的。
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
(这可能会让人感觉有点奇怪,因为一个完全正常的异常继承自一个在请求/响应生命周期之外没有意义的类(。 在您的异常中实现
render
方法,例如:class SpecialException extends Exception { public function render() { return response()->view('errors.403', [], 403); } }
在 \App\Exceptions\Handler 中具有特定的处理行为,例如:
class Handler { // .... public function render($request, $exception) { if ($exception instanceof SpecialException) { return response()->view('errors.403', [], 403); } return parent::render() } }