Laravel でオレオレ例外処理を作成する

## はじめに

これは [Laravel Advent Calendar 2017](https://qiita.com/advent-calendar/2017/laravel) 19日目の記事です。

カスタム例外処理（クラス）の作成と使用方法について書いていきます。

例外処理というと難解そうですが、結構簡単な内容なのですぐ読み終わると思います。

## 環境
- PHP: 7.1.5
- Laravel: 5.5.24

## 前提
- 今回は具体的な例として、**バリデーションエラーの例外クラス**を作成する
- レスポンスは JSON 形式

## 作成手順

### 例外クラスを作成する
- `artisan` コマンドで作成できる
 - `php artisan make:exception ValidationException` 

### 作成した例外クラスを編集する

```app/Exceptions/ValidationException.php
<?php

namespace App\Exceptions;

use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;

class ValidationException extends Exception
{
    public $request;
    public $message;

    public function __construct(Request $request, array $message)
    {
        $this->request = $request;
        // 複数のバリデーションエラー時には , で区切る
        $this->message = implode(',', $message);
    }

    public function report()
    {
        $xRequestId = array_key_exists('x-request-id', $this->request->header()) ? $this->request->header()['x-request-id'][0] : '';
        Log::info(
            $xRequestId,
            [
                'client_ip'      => $this->request->getClientIp(),
                'request_params' => $this->request->all(),
                'response_body'  => $this->message,
            ]
        );
    }

    public function render()
    {
        return response()->json(
            $this->message,
            422
        );
    }
}
```

### 解説
- `report()` ではログとして表示させたい内容を設定している
- `render()` ではレスポンスとして返したい内容を設定している

## 作成した例外クラスを呼ぶ
### 会員登録の `Controller` で呼び出してみる

```app/Http/Controllers/Account/V1/RegisterController.php
<?php

namespace App\Http\Controllers\Account\V1;

use App\Exceptions\ValidationException;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;

class RegisterController extends Controller
{
    public function apply(Request $request)
    {
        $validation = \Validator::make(
            [
                'mail_to'        => $request->get('mail_to'),
                'register_token' => $request->get('register_token'),
            ],
            [
                'mail_to'        => 'required|email|max:128',
                'register_token' => 'required|alpha_num|size:64',
            ]
        );

        if ($validation->fails()) {
            throw new ValidationException($request, $validation->errors()->all());
        }
        
        ...
    }
}    
```

### ルーティング

```routes/account/v1.php
Route::put('registers/apply', 'Account\V1\RegisterController@apply');
```

## 例外を発生させてみる

### 不正なリクエスト
- 必須パラメータである `register token` なし

```
curl -kv -X PUT -H "X-Request-Id: abc123" -d "mail_to=test@gmail.com" https://***-api.***.jp/account/v1/registers/apply
```

## 例外レスポンスの例

```
> 
* upload completely sent off: 37 out of 37 bytes
< HTTP/1.1 422 Unprocessable Entity
< Server: nginx/1.12.1
< Content-Type: application/json
< Transfer-Encoding: chunked
< Connection: keep-alive
< Cache-Control: no-cache, private
< Date: Mon, 11 Dec 2017 09:41:04 GMT
< X-RateLimit-Limit: 60
< X-RateLimit-Remaining: 57
< 
* Curl_http_done: called premature == 0
* Connection #0 to host ***-api.***.jp left intact
"The register token field is required."
```

### 解説

- `register token` がリクエストパラメータに含まれていないため、`The register token field is required.` というようにエラーレスポンスを返している
- ステータスコードも `ValidationException` で設定した `422` になっている

## 例外ログの例

```
[2017-12-11 09:41:04] local.INFO: test123 {"client_ip":"192.168.**.**","request_params":{"mail_to":"test@gmail.com"},"response_body":"The register token field is required."} 
```

## おまけ 
去年の Laravel Advent Calendar にも参加していたので良かったらどうぞ。

[Laravel でカスタムバリデーションとそのテスト](https://qiita.com/komatzz/items/c64c83a004b7f2c63ce6)