もう絶対に間違えない！Laravelでエラーページをカスタマイズするベストプラクティス(5.5)

LaravelでWebアプリケーションを開発するにあたってエラーページのカスタマイズが必要になることがあります。
カスタマイズの仕組みはいくつか用意されているので、表示するエラーの内容や目的別にどの方法を採用するべきなのかまとめてみたいと思います。

#ケース１．想定外のエラーの場合、テスト環境では詳細を表示して本番環境では隠す
##→本番環境では環境変数に APP_DEBUG=false を設定する

Laravelの設定だけで実現可能です。

プロジェクトを作成した段階では.envに

```
APP_DEBUG=true
```

と設定されており、PHPのエラーが発生した際は以下のようなページが表示されます。
（いわゆるWhoops!ページ）

<img width="500" alt="スクリーンショット 2018-02-09 16.20.17.png" src="https://qiita-image-store.s3.amazonaws.com/0/11369/16603af8-8d82-51a6-4fcd-6d8723f41f78.png">

この画面はデバッグにあたって便利な情報が出力されますが、意図せずユーザーに伝わるとまずい情報が表示されてしまう可能性があります。

そこで、本番環境の環境変数あるいは.envに

```
APP_DEBUG=false
```

と設定することで、標準的な500エラーのページに切り替える事ができます。

<img width="500" alt="スクリーンショット 2018-02-09 16.51.09.png" src="https://qiita-image-store.s3.amazonaws.com/0/11369/d2f59b34-0d5e-7e90-59bb-fec336e9b7eb.png">

参考:https://readouble.com/laravel/5.5/ja/errors.html#error-detail

#ケース２．HTTPエラーページのデザインをカスタマイズしたい
##→resources/views/errors ディレクトリにviewファイルを追加する

たとえば、404エラーが発生するとLaravel標準の404エラーページが出力されます。

<img width="500" alt="スクリーンショット 2018-02-09 17.38.49.png" src="https://qiita-image-store.s3.amazonaws.com/0/11369/99bf07ec-51ec-2ad0-abd9-de484a30e163.png">

このままで問題ないこともありますが、多くの場合は他の画面とデザインを統一する必要が出てくるかと思います。
更に、ステータスコードによっては個別のエラーページが用意されていない場合もあります。

そこで、resources/views/errorsディレクトリにステータスコードに対応するviewファイルを追加すると、標準のエラーページに代わって表示することができます。

```
403.blade.php
404.blade.php
500.blade.php
```

ファイル名を"*ステータスコード*.blade.php"の形式にするだけで、その他の設定は特に不要です。
（Laravelは通常のphpファイルもviewとして使用できるので"404.php"などでも構いません）

当然ながら、500エラー用のviewを追加すると、前述の`APP_DEBUG=false`を設定したときのエラー画面も置き換わります。

また、view内でエラー発生時の例外オブジェクトにアクセスするには$exception変数を使用します。

参考: https://readouble.com/laravel/5.5/ja/errors.html#custom-http-error-pages

#ケース３．HTTPエラーの処理を共通化したい
##→app/Exceptions/Handler.php の renderHttpException メソッドをオーバーライドする

全部のHTTPエラーに対応したviewを用意する手間をかけられない場合、
app/Exceptions/Handler.php で renderHttpException メソッドをオーバーライドするとHTTPエラーのレンダリング処理を共通化できます。

``` app/Exceptions/Handler.php
    protected function renderHttpException(HttpException $exception)
    {
        switch ($exception->getStatusCode()){
            case 404:
                $message = "存在しないページです";
                break;
        }

        return response()->view('customer_error', ['message' => $message]); //ビューを返せる
    }
```

ステータスコードは $exception->getStatusCode() で取得することが出来ます。

なお、renderHttpException による共通化は他の方の記事でもまとめられているので一読をおすすめします。
https://qiita.com/M_Ishikawa/items/1f0d72fc93286109464e

#ケース4．特定の例外のときだけ特別なエラーページを表示したい
##→app/Exceptions/Handler.php の render メソッドを修正する

ケース２の対応でHTTPエラーの表示はカスタマイズ出来ますが、PHPの例外のほとんどは500エラーにまとめられてしまいます。
例外ごとに表示を切り分けたい場合は app/Exceptions/Handler.php の render メソッドに処理を書いていく必要があります。

``` app/Exceptions/Handler.php
    /**
     * Render an exception into an HTTP response.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Exception  $exception
     * @return \Illuminate\Http\Response
     */
    public function render($request, Exception $exception)
    {
        if($exception instanceof CustomException){
            return response()->view('custom_error'); //ビューを返せる
        }

        return parent::render($request, $exception);
    }
```

ここで注意すべきなのが、いくつかの例外については基底クラスのrenderメソッドで特別な処理を行っているということです。
例えば、フォームリクエストのバリデーションでエラーになると発生する ValidationException がそのひとつです。
もしも基底クラスのrenderメソッドで ValidationException を処理できなくなるようなコードを書いてしまうと、バリデーションエラーの表示機能が動作しなくなってしまいます。

そのようなバグを避けるために、基底クラスのrenderメソッドの処理を一度確認しておくことをおすすめします。

```Illuminate\Foundation\Exceptions\Handler.php
    /**
     * Render an exception into a response.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Exception  $e
     * @return \Symfony\Component\HttpFoundation\Response
     */
    public function render($request, Exception $e)
    {
        if (method_exists($e, 'render') && $response = $e->render($request)) {
            return Router::toResponse($request, $response);
        } elseif ($e instanceof Responsable) {
            return $e->toResponse($request);
        }

        $e = $this->prepareException($e);

        if ($e instanceof HttpResponseException) {
            return $e->getResponse();
        } elseif ($e instanceof AuthenticationException) {
            return $this->unauthenticated($request, $e);
        } elseif ($e instanceof ValidationException) {
            return $this->convertValidationExceptionToResponse($e, $request);
        }

        return $request->expectsJson()
                        ? $this->prepareJsonResponse($request, $e)
                        : $this->prepareResponse($request, $e);
    }
```

参考： https://readouble.com/laravel/5.5/ja/errors.html#render-method

#最後に
以上、自分が思いつく４ケースについてまとめてみました。
他にも「自分はこうしているよ！」とか「このケースはこうしたらいいよ！」などのご意見あれば是非コメントください。
「このケースはどうしたらいいの？」みたいなのも歓迎です！