Laravel 修改 dingo 异常处理

问题

公司需要将老项目迁移, 由于之前的错误返回都是不规范的, 对于错误处理都是直接返回 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 协议》,转载必须注明作者和本文链接
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 6

看不懂

4年前 评论
JaguarJack (楼主) 4年前
wZzz_98 2年前
JaguarJack (楼主) 2年前

是的,大量抛出500的异常会对nginx造成一定的影响。
对于已知的业务结果大量使用抛出异常,我觉得是不规范的使用方法。
之前根据公司的业务封住给你了一层业务处理结果返回响应信息的一些常用方法,不知是否适合你,laravel-helper

4年前 评论
JaguarJack (楼主) 4年前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!