2021.09.02
STAFF BLOG
スタッフブログ
TECHNICAL
テクログ
どうも!先日恐竜科学博へ行き、少年の心を取り戻したわいです!
いや~、良いですね。恐竜は。図鑑の隅々まで(※1)読み込んでいた幼少期を思い出しました。
ずっと忘れていた恐竜への気持ちを蘇らせてくれたのは、バイリンガルニュースの恐竜くんの回でした。
恐竜科学博もバイリンガルニュースも両方おもしろかったです。
(※1)・・・ティラノサウルスのページのみ
恐竜は化石というかたちで現代人に当時の情報を提供しますが、WebAPIでは一般的にJSONという形式でクライアントに情報を提供します。
しかし、LaravelのデフォルトではエラーレスポンスをJSONで返してくれません。
このままだと、クライアントがデバッグするのは、化石なしで恐竜の研究をするくらい難しいです。
そういうことで、今回はLaravelでAPI / Ajax のエラーレスポンスをJSONで返す方法について書きたいと思います。(※2)
(※2)・・・無理やり話をつなげた
一つ目の方法は、
$request->headers->set('Accept','application/json');
を追加して、APIのいかなるリクエストに対しても Accept:application/json を加えるという方法です。
こうすることで、$request->expectsJson() が true になり、
Illuminate\Foundation\Exceptions\Handler 内でエラーレスポンスをJSON形式で返してくれるようになります。
しかし、この方法の場合、abort(404) をしたときなどに投げられる HttpException や内部エラーを返すときに、
ファイル名などの膨大な情報がJSONに含まれてしまい、とてもキレイなエラーレスポンスとは言えません。
(そもそも、ファイル名などはクライアントに教えたくないです)
参考:Laravel APIで常にJSONをリクエストするミドルウェア
というわけで、下記方法がオススメです。
Illuminate\Foundation\Exceptions\Handler を継承している app\Exceptions\Handler.php(※3)を実装します。
(※3)・・・このクラスは、Laravelのバージョンによって中身が微妙に違います。本記事はLaravel 8.x系です。
<?php
namespace App\Exceptions;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpFoundation\Response as HttpResponse;
use Throwable;
class Handler extends ExceptionHandler
{
/**
* A list of the exception types that are not reported.
*
* @var array
*/
protected $dontReport = [
//
];
/**
* A list of the inputs that are never flashed for validation exceptions.
*
* @var array
*/
protected $dontFlash = [
'current_password',
'password',
'password_confirmation',
];
/**
* Register the exception handling callbacks for the application.
*
* @return void
*/
public function register()
{
$this->reportable(function (Throwable $e) {
//
});
$this->renderable(function (Throwable $e, Request $request) {
if ($request->is('api/*') || $request->ajax())
{
Log::error('[API Error] '.$request->method().': '.$request->fullUrl());
if ($this->isHttpException($e))
{
$message = $e->getMessage() ?: HttpResponse::$statusTexts[$e->getStatusCode()];
Log::error($message);
return response()->json([
'message' => $message
], $e->getStatusCode());
}
elseif ($e instanceof ValidationException)
{
Log::error($e->errors());
return $this->invalidJson($request, $e);
}
else
{
return response()->json([
'message' => 'Internal Server Error'
], 500);
}
}
});
}
}
投げられたすべての例外がこのクラスを通ります。
やっていることは下記です。
・API / AJax リクエストの時だけ、例外処理のレンダーを変える
・abort(401), abort(404, ‘Record not found.’) などの HttpException のエラーメッセージをJSONで返す
・$request->validated()実行時のバリデーションエラーをJSONで返す(ここは、Illuminate\Foundation\Exceptions\Handlerの処理をそのまま使う)
・内部エラーの場合、’Internal Server Error’というエラーメッセージをJSONで返す
・HttpException, ValidationException はデフォルトでログを吐かないようになっているので、API / Ajaxのときはログを吐くようにする
これで、無事クライアントに理解しやすいエラーレスポンスを返すことができるようになりました。
こんな感じで、恐竜も意図的に化石になる部分を決めていたらおもしろいですね。(※4)
(※4)・・・おもしろくない
以上、わいでした。
健闘を祈る!!