我有一个可以抛出异常的 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!');
}
}
}
问题是异常永远不会被捕获。在管道中的某个地方,应用程序捕获异常并打印一个漂亮的错误页面,但它应该由我的中间件捕获,以便它可以正确处理它。
我可以想象我的中间件没有捕获它的唯一方法是,如果它被捕获在管道内部更深处的某个地方,而不是进一步向上/周围,但我在其他中间件或管道执行代码中找不到任何 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
闭包显然这是设计使然的:
是的,这是从L5.2开始的行为。引发异常会导致响应被设置为从异常处理程序返回的响应,然后允许中间件从该点退出。
我觉得这很奇怪。可插入中间件非常适合捕获异常。
仍有两种方法可以做到这一点:
App\Exceptions\Handler
中,这还不够好,因为包裹无法触及它Funky:从响应对象中获取原始异常对象:
$response = $next($request);
$exception = $response->exception;
我也有同样的问题。当阅读 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;
}
查看源代码,您需要捕获 \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);
}
}
如何在不接触
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);
}
}
在
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);
}
}
我想我明白为什么你的代码没有捕获异常。请尝试使用以下代码作为您的处理方法:
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,我相信这是一个更好的地方来处理全局异常。