Laravel 修改 dingo 异常处理默认 Http Code

问题

公司需要将老项目迁移, 由于之前的错误返回都是不规范的, 对于错误处理都是直接返回 200 http code。大量的使用这样的返回。

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

已经没有办法更改了, 客户端都是根据接口的的业务代码 code 来判断的。但是在新的项目中严格规范使用异常抛出, 但是异常抛出后 http code 返回的是 500。导致测试通过不了, 没有办法只能修改。

解决

使用了异常抛出业务错误, 在 laravel 框架中, 异常都是统一处理的, 在 App\Exceptions\Handle 里面进行了全局处理。对于框架抛出的异常, 全部由它来处理。但是当你想要在 Handle 里面修改处理的时候, 你会发现并不会有任何效果。到底什么原因导致的呢?

在入口文件可以看到这么一段代码

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

在这里进行了全局的异常服务注册, 这里先不关注异常在哪里处理的。既然实际是没有通过 Hanle 类处理, 猜测这里服务可能被 Dingo 重新注册过了。带着这个想法可以看一下 Dingo 的服务注册。在 Dingo\Api\Provider\DingoServiceProvider 看到了异常注册

1
2
3
4
5
6
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里面找到。来看一下代码处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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 方法来是查找, 你最终会看到, 过程就不细讲了。

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

这里就和上面的接口类对应了, 为什么返回 500 呢?看这段代码已经一目了然了。如果不是实现了 HttpExceptionInterface 的异常类, StatusCode 都是返回 500。所以需要做的就很简单了, 实现 HttpExceptionInterface 就可以了。简单实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class BaseException extends \Exception  implements HttpExceptionInterface
{

public function getStatusCode()
{
return 200;
}

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

这是一个 exception 基类, 需要的就是继承就可以了。这样就可以解决该问题了。