从中间件中的控制器捕获异常

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!');
    }
  }
}

问题是从未捕获到异常。在管道的某处,应用程序捕获了异常并打印了一个漂亮的错误页面,但它应该被我的中间件捕获,以便它可以正确处理它。

我能想象我的中间件没有捕捉到它的唯一方法是,如果它被捕捉到管道内部更深处的某个地方,而不是更远 up/around,但我在其他中间件中找不到任何 try/catch ,或在管道执行代码中。

这个异常是在哪里捕获的?为什么?

这可能不是一个很好的模式,但我现在不关心它。我比什么都好奇。我是不是完全误解了Laravel的中间件?

我自己的超级简单中间件测试符合我的预期:https://3v4l.org/Udr84 - 在中间件内部捕获并处理异常。

备注:

相关Laravel代码:

我想我明白为什么您的代码没有捕获异常。请尝试使用以下代码作为您的句柄方法:

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 我认为这是一个更好的全局处理异常的地方。

显然this is by design:

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.

我觉得这很奇怪。可插拔中间件非常适合捕获异常。

还有两种方法可以做到这一点:

我遇到了同样的问题。在阅读提到的 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);
    }
}