从中间件中的控制器捕获异常
Catch exception from controller in middleware
我有一个可以抛出异常的 laravel 控制器,以及一个可以捕获该异常的全局中间件。在半伪代码中:
// App\Controllers\...
class Controller {
function store() {
throw new FormException; // via validation etc, but it's thrown here
}
}
// App\Http\Middleware\...
class Middleware {
function handle(Closure $next) {
try {
// Breakpoint 1
return $next(); // $response
// Breakpoint 2
}
catch (FormException $ex) {
// Breakpoint 3
exit('FormException caught!');
}
}
}
问题是从未捕获到异常。在管道的某处,应用程序捕获了异常并打印了一个漂亮的错误页面,但它应该被我的中间件捕获,以便它可以正确处理它。
- 断点 1 应该会触发,它确实<<很好
- 断点 2 不应触发,而且它<<很好
- 断点 3 应该触发,但它没有<<什么??
我能想象我的中间件没有捕捉到它的唯一方法是,如果它被捕捉到管道内部更深处的某个地方,而不是更远 up/around,但我在其他中间件中找不到任何 try/catch ,或在管道执行代码中。
这个异常是在哪里捕获的?为什么?
这可能不是一个很好的模式,但我现在不关心它。我比什么都好奇。我是不是完全误解了Laravel的中间件?
我自己的超级简单中间件测试符合我的预期:https://3v4l.org/Udr84 - 在中间件内部捕获并处理异常。
备注:
$response
对象($next()
的return值)是处理过的异常页面,所以已经处理过了。在哪里以及为什么?
- 在
App\Exceptions\Handler::render()
中处理异常是可行的,但我希望所有逻辑都在中间件包中,而不是在应用程序代码中。
相关Laravel代码:
Kernel::handle()
启动中间件管道 << 这有一个包罗万象的 catch(),但我的 catch() 是第一个,对吗?
Pipeline::then()
开始执行中间件
Pipeline::getSlice()
处理并创建 $next
闭包
我想我明白为什么您的代码没有捕获异常。请尝试使用以下代码作为您的句柄方法:
function handle(Closure $next) {
try {
// Breakpoint 1
$response = $next();
// Breakpoint 2
}
catch (FormException $ex) {
// Breakpoint 3
exit('FormException caught!');
}
return $response;
}
上面的代码尚未经过测试,但是当您查看 Laravel 文档时,您可以在返回响应之前看到,您应该执行您的代码(在这种情况下,您的异常处理逻辑)。请查看:Laravel - Defining Middleware 了解有关中间件前后定义的更多信息。
顺便说一句,也看看这个文件:Laravel/app/Exceptions/Handler.php 我认为这是一个更好的全局处理异常的地方。
Yes, this is the beavhiour starting from L5.2. Throwing an exception causes the response to be set as that returned from the exception handler, and then the middleware is allowed to backout from that point.
我觉得这很奇怪。可插拔中间件非常适合捕获异常。
还有两种方法可以做到这一点:
- 正确:在
App\Exceptions\Handler
,这还不够好,因为包裹不能碰那个
时髦:take the original exception object from the response object:
$response = $next($request);
$exception = $response->exception;
我遇到了同样的问题。在阅读提到的 thread Rudie 时,他们提供了一个对我有用的可能解决方案:
public function handle(Request $request, Closure $next) {
$response = $next($request);
// 'Catch' our FormValidationException and redirect back.
if (!empty($response->exception) && $response->exception instanceof FormValidationException) {
return redirect()->back()->withErrors($response->exception->form->getErrors())->withInput();
}
return $response;
}
如何在不接触 App\Exceptions\Handler
文件的情况下捕获错误:
注册您的CustomExceptionHandler
/* @var ExceptionHandler Illuminate\Contracts\Debug\ExceptionHandler */
$previousHandler = null;
if (app()->bound(ExceptionHandler::class) === true) {
$previousHandler = app()->make(ExceptionHandler::class);
}
app()->singleton(ExceptionHandler::class, function () use ($previousHandler) {
return new CustomExceptionHandler($previousHandler);
});
还有你的基础CustomExceptionHandler
class CustomExceptionHandler implements ExceptionHandlerInterface
{
/**
* @var ExceptionHandlerInterface|null
*/
private $previous;
public function __construct(ExceptionHandlerInterface $previous = null)
{
$this->previous = $previous;
}
public function report(Exception $exception)
{
$this->previous === null ?: $this->previous->report($exception);
}
public function render($request, Exception $exception)
{
if ($exception instanceof CustomExceptionHandler) {
echo 'This is my particular way to show my errors';
} else {
$response = $this->previous === null ? null : $this->previous->render($request, $exception);
}
return $response;
}
/**
* {@inheritdoc}
*/
public function renderForConsole($output, Exception $exception)
{
/* @var OutputInterface $output */
$this->previous === null ?: $this->previous->renderForConsole($output, $exception);
}
}
查看源代码,您需要捕获 \Exception 和 \Throwable 以便您的 try catch 能够在您的中间件中正常工作。这适用于 Laravel 5.8
class TryCatchMiddleware
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
try {
if ( somethingThatCouldThrowAnException() ) {
$request->newVariable = true;
}
} catch (\Exception $e) {
// do nothing
} catch (\Throwable $e) {
// do nothing
}
return $next($request);
}
}
我有一个可以抛出异常的 laravel 控制器,以及一个可以捕获该异常的全局中间件。在半伪代码中:
// App\Controllers\...
class Controller {
function store() {
throw new FormException; // via validation etc, but it's thrown here
}
}
// App\Http\Middleware\...
class Middleware {
function handle(Closure $next) {
try {
// Breakpoint 1
return $next(); // $response
// Breakpoint 2
}
catch (FormException $ex) {
// Breakpoint 3
exit('FormException caught!');
}
}
}
问题是从未捕获到异常。在管道的某处,应用程序捕获了异常并打印了一个漂亮的错误页面,但它应该被我的中间件捕获,以便它可以正确处理它。
- 断点 1 应该会触发,它确实<<很好
- 断点 2 不应触发,而且它<<很好
- 断点 3 应该触发,但它没有<<什么??
我能想象我的中间件没有捕捉到它的唯一方法是,如果它被捕捉到管道内部更深处的某个地方,而不是更远 up/around,但我在其他中间件中找不到任何 try/catch ,或在管道执行代码中。
这个异常是在哪里捕获的?为什么?
这可能不是一个很好的模式,但我现在不关心它。我比什么都好奇。我是不是完全误解了Laravel的中间件?
我自己的超级简单中间件测试符合我的预期:https://3v4l.org/Udr84 - 在中间件内部捕获并处理异常。
备注:
$response
对象($next()
的return值)是处理过的异常页面,所以已经处理过了。在哪里以及为什么?- 在
App\Exceptions\Handler::render()
中处理异常是可行的,但我希望所有逻辑都在中间件包中,而不是在应用程序代码中。
相关Laravel代码:
Kernel::handle()
启动中间件管道 << 这有一个包罗万象的 catch(),但我的 catch() 是第一个,对吗?Pipeline::then()
开始执行中间件Pipeline::getSlice()
处理并创建$next
闭包
我想我明白为什么您的代码没有捕获异常。请尝试使用以下代码作为您的句柄方法:
function handle(Closure $next) {
try {
// Breakpoint 1
$response = $next();
// Breakpoint 2
}
catch (FormException $ex) {
// Breakpoint 3
exit('FormException caught!');
}
return $response;
}
上面的代码尚未经过测试,但是当您查看 Laravel 文档时,您可以在返回响应之前看到,您应该执行您的代码(在这种情况下,您的异常处理逻辑)。请查看:Laravel - Defining Middleware 了解有关中间件前后定义的更多信息。
顺便说一句,也看看这个文件:Laravel/app/Exceptions/Handler.php 我认为这是一个更好的全局处理异常的地方。
Yes, this is the beavhiour starting from L5.2. Throwing an exception causes the response to be set as that returned from the exception handler, and then the middleware is allowed to backout from that point.
我觉得这很奇怪。可插拔中间件非常适合捕获异常。
还有两种方法可以做到这一点:
- 正确:在
App\Exceptions\Handler
,这还不够好,因为包裹不能碰那个 时髦:take the original exception object from the response object:
$response = $next($request); $exception = $response->exception;
我遇到了同样的问题。在阅读提到的 thread Rudie 时,他们提供了一个对我有用的可能解决方案:
public function handle(Request $request, Closure $next) {
$response = $next($request);
// 'Catch' our FormValidationException and redirect back.
if (!empty($response->exception) && $response->exception instanceof FormValidationException) {
return redirect()->back()->withErrors($response->exception->form->getErrors())->withInput();
}
return $response;
}
如何在不接触 App\Exceptions\Handler
文件的情况下捕获错误:
注册您的CustomExceptionHandler
/* @var ExceptionHandler Illuminate\Contracts\Debug\ExceptionHandler */
$previousHandler = null;
if (app()->bound(ExceptionHandler::class) === true) {
$previousHandler = app()->make(ExceptionHandler::class);
}
app()->singleton(ExceptionHandler::class, function () use ($previousHandler) {
return new CustomExceptionHandler($previousHandler);
});
还有你的基础CustomExceptionHandler
class CustomExceptionHandler implements ExceptionHandlerInterface
{
/**
* @var ExceptionHandlerInterface|null
*/
private $previous;
public function __construct(ExceptionHandlerInterface $previous = null)
{
$this->previous = $previous;
}
public function report(Exception $exception)
{
$this->previous === null ?: $this->previous->report($exception);
}
public function render($request, Exception $exception)
{
if ($exception instanceof CustomExceptionHandler) {
echo 'This is my particular way to show my errors';
} else {
$response = $this->previous === null ? null : $this->previous->render($request, $exception);
}
return $response;
}
/**
* {@inheritdoc}
*/
public function renderForConsole($output, Exception $exception)
{
/* @var OutputInterface $output */
$this->previous === null ?: $this->previous->renderForConsole($output, $exception);
}
}
查看源代码,您需要捕获 \Exception 和 \Throwable 以便您的 try catch 能够在您的中间件中正常工作。这适用于 Laravel 5.8
class TryCatchMiddleware
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
try {
if ( somethingThatCouldThrowAnException() ) {
$request->newVariable = true;
}
} catch (\Exception $e) {
// do nothing
} catch (\Throwable $e) {
// do nothing
}
return $next($request);
}
}