Laravel 修改 dingo 異常處理

JaguarJack發表於2019-05-05

問題

公司需要將老專案遷移, 由於之前的錯誤返回都是不規範的, 對於錯誤處理都是直接返回 200 http code。大量的使用這樣的返回。

response()->json(200, ['msg'=>$msg])

已經沒有辦法更改了, 客戶端都是根據介面的的業務程式碼 code 來判斷的。但是在新的專案中嚴格規範使用異常丟擲, 但是異常丟擲後 http code 返回的是 500。導致測試通過不了, 沒有辦法只能修改。

解決

使用了異常丟擲業務錯誤, 在 laravel 框架中, 異常都是統一處理的, 在 App\Exceptions\Handle 裡面進行了全域性處理。對於框架丟擲的異常, 全部由它來處理。但是當你想要在 Handle 裡面修改處理的時候, 你會發現並不會有任何效果。到底什麼原因導致的呢?

在入口檔案可以看到這麼一段程式碼

$app->singleton(
    Illuminate\Contracts\Debug\ExceptionHandler::class,
    App\Exceptions\Handler::class
);

在這裡進行了全域性的異常服務註冊, 這裡先不關注異常在哪裡處理的。既然實際是沒有通過 Hanle 類處理, 猜測這裡服務可能被 Dingo 重新註冊過了。帶著這個想法可以看一下 Dingo 的服務註冊。在 Dingo\Api\Provider\DingoServiceProvider 看到了異常註冊

protected function registerExceptionHandler()
    {
        $this->app->singleton('api.exception', function ($app) {
            return new ExceptionHandler($app['Illuminate\Contracts\Debug\ExceptionHandler'], $this->config('errorFormat'), $this->config('debug'));
        });
    }

這裡肯定會有疑問了, 這個單例模式也沒有重新註冊 Illuminate\Contracts\Debug\ExceptionHandler::class 這個類啊?沒錯, 因為 Dingo 接管了路由, 你實際使用的 \Dingo\Api\Routing\Router::class, 所以 Dispatch 的會由 Dingo 來執行。這裡就不細說了。有興趣的可以看一下 Dingo 的處理。
Dispatch 處理可以在 Dingo\Api\Dispatcher裡面找到。來看一下程式碼處理。

protected function dispatch(InternalRequest $request)
    {
        $this->routeStack[] = $this->router->getCurrentRoute();

        $this->clearCachedFacadeInstance();

        try {
            $this->container->instance('request', $request);

            $response = $this->router->dispatch($request);

            if (! $response->isSuccessful() && ! $response->isRedirection()) {
                throw new InternalHttpException($response);
            } elseif (! $this->raw) {
                $response = $response->getOriginalContent();
            }
            // 主要看這裡的 exception 處理 這裡就是接管的異常
        } catch (HttpExceptionInterface $exception) {
            $this->refreshRequestStack();

            throw $exception;
        }

        $this->refreshRequestStack();

        return $response;
    }

先不用管這個介面類的異常, 來看看 Dingo 的 Handle 如何處理的。下面會提到處理為何這裡是介面, 還有該如何修改。

你需要從 render 方法來是查詢, 你最終會看到, 過程就不細講了。

protected function getExceptionStatusCode(Exception $exception, $defaultStatusCode = 500)
    {
        return ($exception instanceof HttpExceptionInterface) ? $exception->getStatusCode() : $defaultStatusCode;
    }

這裡就和上面的介面類對應了, 為什麼返回 500 呢?看這段程式碼已經一目瞭然了。如果不是實現了 HttpExceptionInterface 的異常類, StatusCode 都是返回 500。所以需要做的就很簡單了, 實現 HttpExceptionInterface 就可以了。簡單實現:

class BaseException extends \Exception  implements HttpExceptionInterface
{

    public function getStatusCode()
    {
        return 200;
    }

    /**
     * Returns response headers.
     *
     * @return array Response headers
     */
    public function getHeaders()
    {
        return [];
    }

這是一個 exception 基類, 需要的就是繼承就可以了。這樣就可以解決該問題了

原文連結

本作品採用《CC 協議》,轉載必須註明作者和本文連結