在 Laravel 中间件中处理异常和致命错误

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

我目前正在使用 Laravel 7.30.6 开发一个 API,如果出现任何问题,我希望能够处理所有错误并在所有 API 路由上返回带有 HTTP 代码 500 的内部服务器错误,但我想保留用于非 API 相关请求的默认错误处理程序 (

app/Exceptions/Handler.php
)。

为此,我创建了一个新的中间件

HandleErrors

<?php

// app\Http\Middleware\HandleErrors.php

namespace App\Http\Middleware;

use Illuminate\Http\Request;
use Response;
use Closure;
use Log;

class HandleErrors
{
    public function handle(Request $request, Closure $next)
    {
        try
        {
            return $next($request);
        }
        catch (\Throwable $th)
        {
            Log::error($th);
            return Response::json(array("message"=>"Internal server error"), 500);
        }
    }
}

我注册了中间件

<?php

// app\Http\Kernel.php

namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{
    [...]

    /**
     * The application's route middleware.
     *
     * These middleware may be assigned to groups or used individually.
     *
     * @var array
     */
    protected $routeMiddleware = [
        [...]
        'handle-errors' => \App\Http\Middleware\HandleErrors::class,
    ];
}

我使用这个中间件调用测试控制器创建了一条新路线

<?php

// routes/api.php

use Illuminate\Support\Facades\Route;

/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
// */

Route::middleware(["handle-errors"])->group(function () {
    Route::get("/v1/test", "API\\v1\TestController@test");
});

进入测试控制器,我自愿生成错误,例如除以零

<?php

// app/Http/Controllers/API/v1/TestController.php

namespace App\Http\Controllers\API\v1;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Response;

use Log;

class TestController extends Controller {

    public function test(Request $request)
    {
        //$a = 10 / 0;
        return Response::json(array("message"=>"SUCCESS"), 200);
    }
}

当评论除以零时,我确实收到了“成功”响应。但是,当取消注释时,中间件不会处理异常,并且我收到错误堆栈作为响应。

[...]

<!--
ErrorException: Division by zero in file /var/www/html/my_project/app/Http/Controllers/API/v1/TestController.php on line 15

#0 /var/www/html/my_project/app/Http/Controllers/API/v1/TestController.php(15): Illuminate\Foundation\Bootstrap\HandleExceptions-&gt;handleError()
#1 /var/www/html/my_project/vendor/laravel/framework/src/Illuminate/Routing/Controller.php(54): App\Http\Controllers\API\v1\TestController-&gt;test()
#2 /var/www/html/my_project/vendor/laravel/framework/src/Illuminate/Routing/ControllerDispatcher.php(45): Illuminate\Routing\Controller-&gt;callAction()
#3 /var/www/html/my_project/vendor/laravel/framework/src/Illuminate/Routing/Route.php(239): Illuminate\Routing\ControllerDispatcher-&gt;dispatch()
#4 /var/www/html/my_project/vendor/laravel/framework/src/Illuminate/Routing/Route.php(196): Illuminate\Routing\Route-&gt;runController()
#5 /var/www/html/my_project/vendor/laravel/framework/src/Illuminate/Routing/Router.php(685): Illuminate\Routing\Route-&gt;run()
#6 /var/www/html/my_project/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(128): Illuminate\Routing\Router-&gt;Illuminate\Routing\{closure}()
#7 /var/www/html/my_project/app/Http/Middleware/HandleErrors.php(16): Illuminate\Pipeline\Pipeline-&gt;Illuminate\Pipeline\{closure}()
#8 /var/www/html/my_project/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): App\Http\Middleware\HandleErrors-&gt;handle()
#9 /var/www/html/my_project/vendor/laravel/framework/src/Illuminate/Routing/Middleware/SubstituteBindings.php(41): Illuminate\Pipeline\Pipeline-&gt;Illuminate\Pipeline\{closure}()
#10 /var/www/html/my_project/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\Routing\Middleware\SubstituteBindings-&gt;handle()
#11 /var/www/html/my_project/vendor/laravel/framework/src/Illuminate/Routing/Middleware/ThrottleRequests.php(59): Illuminate\Pipeline\Pipeline-&gt;Illuminate\Pipeline\{closure}()
#12 /var/www/html/my_project/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\Routing\Middleware\ThrottleRequests-&gt;handle()
#13 /var/www/html/my_project/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(103): Illuminate\Pipeline\Pipeline-&gt;Illuminate\Pipeline\{closure}()
#14 /var/www/html/my_project/vendor/laravel/framework/src/Illuminate/Routing/Router.php(687): Illuminate\Pipeline\Pipeline-&gt;then()
#15 /var/www/html/my_project/vendor/laravel/framework/src/Illuminate/Routing/Router.php(662): Illuminate\Routing\Router-&gt;runRouteWithinStack()
#16 /var/www/html/my_project/vendor/laravel/framework/src/Illuminate/Routing/Router.php(628): Illuminate\Routing\Router-&gt;runRoute()
#17 /var/www/html/my_project/vendor/laravel/framework/src/Illuminate/Routing/Router.php(617): Illuminate\Routing\Router-&gt;dispatchToRoute()
#18 /var/www/html/my_project/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(165): Illuminate\Routing\Router-&gt;dispatch()
#19 /var/www/html/my_project/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(128): Illuminate\Foundation\Http\Kernel-&gt;Illuminate\Foundation\Http\{closure}()
#20 /var/www/html/my_project/app/Http/Middleware/corsMiddleware.php(16): Illuminate\Pipeline\Pipeline-&gt;Illuminate\Pipeline\{closure}()
#21 /var/www/html/my_project/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): App\Http\Middleware\corsMiddleware-&gt;handle()
#22 /var/www/html/my_project/vendor/pragmarx/tracker/src/Vendor/Laravel/Middlewares/Tracker.php(24): Illuminate\Pipeline\Pipeline-&gt;Illuminate\Pipeline\{closure}()
#23 /var/www/html/my_project/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): PragmaRX\Tracker\Vendor\Laravel\Middlewares\Tracker-&gt;handle()
#24 /var/www/html/my_project/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\Pipeline\Pipeline-&gt;Illuminate\Pipeline\{closure}()
#25 /var/www/html/my_project/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\Foundation\Http\Middleware\TransformsRequest-&gt;handle()
#26 /var/www/html/my_project/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/TransformsRequest.php(21): Illuminate\Pipeline\Pipeline-&gt;Illuminate\Pipeline\{closure}()
#27 /var/www/html/my_project/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\Foundation\Http\Middleware\TransformsRequest-&gt;handle()
#28 /var/www/html/my_project/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/ValidatePostSize.php(27): Illuminate\Pipeline\Pipeline-&gt;Illuminate\Pipeline\{closure}()
#29 /var/www/html/my_project/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\Foundation\Http\Middleware\ValidatePostSize-&gt;handle()
#30 /var/www/html/my_project/vendor/laravel/framework/src/Illuminate/Foundation/Http/Middleware/CheckForMaintenanceMode.php(63): Illuminate\Pipeline\Pipeline-&gt;Illuminate\Pipeline\{closure}()
#31 /var/www/html/my_project/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode-&gt;handle()
#32 /var/www/html/my_project/vendor/fruitcake/laravel-cors/src/HandleCors.php(37): Illuminate\Pipeline\Pipeline-&gt;Illuminate\Pipeline\{closure}()
#33 /var/www/html/my_project/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Fruitcake\Cors\HandleCors-&gt;handle()
#34 /var/www/html/my_project/vendor/fideloper/proxy/src/TrustProxies.php(57): Illuminate\Pipeline\Pipeline-&gt;Illuminate\Pipeline\{closure}()
#35 /var/www/html/my_project/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(167): Fideloper\Proxy\TrustProxies-&gt;handle()
#36 /var/www/html/my_project/vendor/laravel/framework/src/Illuminate/Pipeline/Pipeline.php(103): Illuminate\Pipeline\Pipeline-&gt;Illuminate\Pipeline\{closure}()
#37 /var/www/html/my_project/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(140): Illuminate\Pipeline\Pipeline-&gt;then()
#38 /var/www/html/my_project/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(109): Illuminate\Foundation\Http\Kernel-&gt;sendRequestThroughRouter()
#39 /var/www/html/my_project/public/index.php(55): Illuminate\Foundation\Http\Kernel-&gt;handle()
#40 {main}
-->
</body>
</html>

如果我用 try/catch 块包围

test
函数代码,我确实会收到预期的 HTTP 错误 500。

    public function test(Request $request)
    {
        try
        {
            $a = 10 / 0;
            return Response::json(array("message"=>"SUCCESS"), 200);
        }
        catch (\Throwable $th)
        {
            Log::error($th);
            return Response::json(array("message"=>"Internal server error"), 500);
        }
    }

我想避免将所有 API 函数放入 try/catch 块中,并将异常直接处理到中间件中。

我还意识到有些错误没有得到正确处理,例如,如果我创建了一个语法错误,则该错误在 catch 块中没有得到正确处理

所以我的问题如下:

  • 为什么异常在控制器中被正确处理,但在中间件中却没有被正确处理?
  • 如何处理语法错误(以及所有可能的异常和错误)并返回 HTTP 代码 500 的响应?
php laravel api exception laravel-middleware
1个回答
0
投票

让我给你一个更好的主意。

您可以创建一个新的

Hander
类来传递给 Laravel,而不是它自己的
Handler
类。

我们称这个新类为

ApiHandler
,它就位于
App/Exceptions/Handler.php
旁边。 (请注意,
ApiHandler
,扩展了 Laravel 的
Handler
类。)

你可以在

ApiHandler
类中使用这个逻辑:

<?php

namespace App\Exceptions;

use \Throwable;
use Illuminate\Http\JsonResponse;

class ApiHandler extends Handler
{
    /**
     * Render an exception into an HTTP response.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Throwable  $e
     * @return \Symfony\Component\HttpFoundation\Response
     */
    public function render($request, Throwable $e)
    {
        return $this->shouldReturnJson($request, $e) ?
            $this->prepareJsonResponse($request, $e) :
            $this->prepareResponse($request, $e);
    }

    /**
     * Prepare a JSON response for the given exception.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Throwable  $e
     * @return \Illuminate\Http\JsonResponse
     */
    protected function prepareJsonResponse($request, Throwable $e)
    {
        $data = $this->convertExceptionToArray($e);

        return new JsonResponse(
            $this->convertExceptionToArray($e), 
            $data['status'] ?? 500, 
            $this->isHttpException($e) ? $e->getHeaders() : [], 
            JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES
        );
    }

    /**
     * Convert the given exception to an array.
     *
     * @param  \Throwable  $e
     * @return array
     */
    protected function convertExceptionToArray(Throwable $e)
    {
        $response = [
            'code' => $this->isHttpException($e) ? $e->getCode() : 500,
            'message' => $e->getMessage() ?? 'Internal Server Error',
        ];

        if (env('APP_DEBUG')) {
            $response ['file'] = $e->getFile();
            $response ['line'] = $e->getLine();
            $response ['trace'] = $e->getTrace();
        }

        return $response;
    }
}

现在你必须告诉 Laravel 使用这个类,而不是它自己的类。

前往

AppServiceProvider
并注册
ApiHandler

    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        // Registering api handler instead of 
        // Laravel's built-in one. (Comment if want to revert)
        $this->app->bind(
            \Illuminate\Contracts\Debug\ExceptionHandler::class,
            \App\Exceptions\ApiHandler::class
        );
    }

现在每次你的 API 捕获异常时,它都会使用这个类并为你提供你想要的响应结构。请注意,我添加了一些细节,以便为您和您的同事创造更好的开发人员体验。

干杯。

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