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

问题描述 投票:0回答:6

我有一个可以抛出异常的 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 应该触发,并且确实如此 << good
  • 断点 2 不应该触发,而且也确实没有 << good
  • 断点 3 应该触发,但它没有 << what??

我可以想象我的中间件没有捕获它的唯一方法是,如果它被捕获在管道内部更深处的某个地方,而不是进一步向上/周围,但我在其他中间件或管道执行代码中找不到任何 try/catch 。

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

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

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

备注:

  • $response
    对象(
    $next()
    的返回值)是已处理的异常页面,因此已经被处理。在哪里以及为什么?
  • 处理
    App\Exceptions\Handler::render()
    中的异常是有效的,但我希望所有逻辑都位于中间件包中,而不是应用程序代码中。

相关 Laravel 代码:

  • Kernel::handle()
    启动中间件管道<< this has a catch-all catch(), but my catch() comes first, right?
  • Pipeline::then()
    启动中间件执行
  • Pipeline::getSlice()
    处理并创建
    $next
    闭包
exception laravel-5 middleware pipeline
6个回答
19
投票

显然这是设计使然的

是的,这是从L5.2开始的行为。引发异常会导致响应被设置为从异常处理程序返回的响应,然后允许中间件从该点退出。

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

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


6
投票

我也有同样的问题。当阅读 Rudie 提到的thread时,他们给出了一个对我有用的可能解决方案:

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;
}

3
投票

查看源代码,您需要捕获 \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);
    }
}

0
投票

如何在不接触

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

0
投票

middleware
内部进行更清洁的方法如下:

use Illuminate\Support\Facades\Log;
use App\Providers\RouteServiceProvider;

// ... other code

function handle(Closure $next) {
    try {
      // ... your logic
      return $next($request);
    }
    catch (\Throwable $th) {
      //Logging exception in laravel.log file so it can be handled cleanly
      Log::error("Exception occured in xyz middleware: " . $th->getMessage());

      // Now returning the response based on request type
      return $request->expectsJson()
                ? response()->json('Not a valid request.', 400)
                : redirect(RouteServiceProvider::HOME);
      
    }
  }

-3
投票

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

function handle(Closure $next) {
try {
  // Breakpoint 1
  $response = $next();
  // Breakpoint 2
}
catch (FormException $ex) {
  // Breakpoint 3
  exit('FormException caught!');
}
return $response;
}

上面的代码尚未经过测试,但当您查看 Laravel 文档时,您会发现在返回响应之前,您应该执行您的代码(在本例中为异常处理逻辑)。请查看:Laravel - 定义中间件,了解有关中间件定义之前和之后的更多信息。

顺便说一句,还要看看这个文件:Laravel/app/Exceptions/Handler.php,我相信这是一个更好的地方来处理全局异常。

© www.soinside.com 2019 - 2024. All rights reserved.