LaravelでAPIのエラーレスポンスを共通化する

## はじめに

Laravel で API を開発していると、フロントの都合の良いようにエラーのレスポンスを共通化しなければなりません。
色々な方法があるかと思いますが、今回はその一つをご紹介します。

## どうするか

1. エラーのキャッチ
2. API へのアクセスかを判定
3. 例外ごとに整形
4. json で返却

## 実装

以下のように実装しました。順に解説します。

```php:Handler.php
public function register(): void
{
    // 1. エラーのキャッチ
    $this->renderable(function (Throwable $e, $request) {
        // 2. APIへのアクセスかを判定
        if ($this->shouldReturnJson($request, $e)) {
            $response = [];
            // 3. 例外ごとに整形
            switch (true) {
                case $e instanceof AuthenticationException:
                    $statusCode = 401;
                    $response['message'] = '認証エラーが発生しました。';
                    break;
                case $e instanceof ValidationException:
                    $statusCode = $e->status;
                    $response['message'] = 'リクエストが不正です。';
                    $errors = [];
                    foreach ($e->validator->errors()->getMessages() as $key => $value) {
                        $errors[] = [
                            'property' => $key,
                            'message' => $value[0]
                        ];
                    }
                    $response['errors'] = $errors;
                    break;
                default:
                    if (method_exists($e, 'getStatusCode')) {
                        $statusCode = $e->getStatusCode();
                    } else {
                        $statusCode = 500;
                    }
                    $response['message'] = 'サーバーエラーが発生しました。';

            }
            if (config('app.debug')) {
                $response['trace'] = $e->getTrace();
                $response['code'] = $e->getCode();
            }
            // 4. jsonで返却
            return response()->json($response, $statusCode);
        }
        return null;
    });
}
```

### 1. エラーのキャッチ

```php:Handler.php
// 1. エラーのキャッチ
$this->renderable(function (Throwable $e, $request)
```

Laravel ではアプリケーションが投げるすべての例外を Handler.php で取得できます。
ここでは renderable を使用してエラー発生時の分岐や動作を登録します。

### 2. API へのアクセスかを判定

```php:Handler.php
// 2. APIへのアクセスかを判定
if ($this->shouldReturnJson($request, $e))
```

shouldReturnJson でリクエストヘッダーに`Accept: application/json`がある場合 API へのアクセスとみなします。
公式サイトのサンプルでは`$request->is('api/*')`で判定しているのでそちらでも良いと思います。

### 3. 例外ごとに整形

```php:Handler.php
case $e instanceof ValidationException:
    $statusCode = $e->status;
    $response['message'] = 'リクエストが不正です。';
    $errors = [];
    foreach ($e->validator->errors()->getMessages() as $key => $value) {
        $errors[] = [
            'property' => $key,
            'message' => $value[0]
        ];
    }
    $response['errors'] = $errors;
    break;
```

発生した例外ごとにステータスコードと任意のエラーメッセージを設定しています。
例ではバリデーションエラーが発生した際の個別メッセージにも対応しています。

今回は以下のように開発環境に限りどこで例外が発生したかを追えるようにもしました。

```php:Handler.php
if (config('app.debug')) {
    $response['trace'] = $e->getTrace();
    $response['code'] = $e->getCode();
}
```

### 4. json で返却

```php:Handler.php
return response()->json($response, $statusCode);
```

ステータスコードとエラーメッセージを json で返却します。

## 結果

エラーはこのように返却されます。
```json
{
    "message": "リクエストが不正です。",
    "errors": [
        {
            "property": "test",
            "message": "testは必ず指定してください。"
        }
    ]
}
```


## さいごに

そこまで複雑にならずエラーレスポンスの共通化ができました。
今回は一部のみ例で示しましたが実際は case がもっと増えると思います。
誰かのお役に立てれば幸いです。