Laravel5: エラーページを共通化〜どんなステータスコードでもどんと来い！

[2015 Laravel Advent Calendar](http://www.adventar.org/calendars/817) 14日目！ Laravel大好き @M_Ishikawa です。

さて、[1日目の記事](http://qiita.com/M_Ishikawa/items/00785025383e98296b7c)ではエラーログについての内容でしたが、今回もエラーに関する内容です。

エラーページをカスタマイズしようとググればいくらでも答えが見つかります。
それらは

```
resources/views/errors/404.blade.php
```
を編集すればいいとか、他のステータスコードに対応させたければ404を該当ステータスコードに変えたものを用意すればいいとか、です。

```
resources/views/errors/404.blade.php
resources/views/errors/500.blade.php
resources/views/errors/503.blade.php
...
```

うーん。

<b>1つのテンプレートファイルで済ませたい！！</b>

みんなそう思わないのかな？ぼくは激しく思います！でもググっても日本語サイトではでてきません。1つのステータスコードに強制したりするのは見かけたけど、いやいやそれじゃヘッダ変わっちゃうじゃん〜。

というわけで、コード追っかければ簡単に答えが見つかるのがLaravelの大きな魅力の1つ。レッツ・トライ！

結論を急ぐ方は [ここ](#answer)

※なお、Laravel5のコードで説明しています。

<br>

----

<br>

まず、エラーページをレンダリングするのは [app/Exceptions/Handler.php の render()](https://github.com/laravel/laravel/blob/b0a22fc51161630c81f20e23a9269b82a369953e/app/Exceptions/Handler.php#L43-L50) です。


```php:app/Exceptions/Handler.php
    public function render($request, Exception $e)
    {
        if ($e instanceof ModelNotFoundException) {
            $e = new NotFoundHttpException($e->getMessage(), $e);
        }

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

親を追っかけます。

[vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/Handler.php の render()](https://github.com/laravel/framework/blob/ca2d794d2800e53391c51d117a133f2c2e6dd146/src/Illuminate/Foundation/Exceptions/Handler.php#L89-L100)

```php:vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/Handler.php
    public function render($request, Exception $e)
    {
        if ($this->isUnauthorizedException($e)) {
            $e = new HttpException(403, $e->getMessage());
        }
        if ($this->isHttpException($e)) {
            return $this->toIlluminateResponse($this->renderHttpException($e), $e);
        } else {
            return $this->toIlluminateResponse($this->convertExceptionToResponse($e), $e);
        }
    }
```

ここでエラーページテンプレートを指定しているのは `renderHttpException()` ですね。

[vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/Handler.php の renderHttpException()](https://github.com/laravel/framework/blob/ca2d794d2800e53391c51d117a133f2c2e6dd146/src/Illuminate/Foundation/Exceptions/Handler.php#L136-L145)

```php:vendor/laravel/framework/src/Illuminate/Foundation/Exceptions/Handler.php
    protected function renderHttpException(HttpException $e)
    {
        $status = $e->getStatusCode();
        if (view()->exists("errors.{$status}")) {
            return response()->view("errors.{$status}", ['exception' => $e], $status);
        } else {
            return $this->convertExceptionToResponse($e);
        }
    }
```

発見しました！
`"errors.{$status}"` というふうにベタ書きされています。操作するにはこの関数をオーバーライドするだけでよさそうです。`app/Exceptions/Handler.php` の下の方にコピー&貼り付けして編集します。テンプレートファイル名を `common.blade.php` にしたいので

<a name="answer"></a>

```php:app/Exceptions/Handler.php
    /**
     * 共通エラーページ
     */
    protected function renderHttpException(\Symfony\Component\HttpKernel\Exception\HttpException $e)
    {
        $status = $e->getStatusCode();
        return response()->view("errors.common", ['exception' => $e], $status);
    }
```

を追記すればOKです。

ここで気づくのですが、エラーページには `$exception` でHttpExceptionが渡るのですね。ステータスコードやエラーメッセージを渡さないといけないと思っていたのですが、そんなことしなくてもこれをこのまま使うことができます。

というわけで共通化のエラーページはこんな感じにしてみました。本来はphpのコードの部分は上記の `app/Exceptions/Handler.php` に入れてしまうべきかもですがここではあえてbladeに直書きしてみます。

```php:resources/views/errors/common.blade.php
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
</head>
<body>
@php
$status_code = $exception->getStatusCode();
$message = $exception->getMessage();

if (! $message) {
    switch ($status_code) {
        case 400:
            $message = 'Bad Request';
            break;
        case 401:
            $message = '認証に失敗しました';
            break;
        case 403:
            $message = 'アクセス権がありません';
            break;
        case 404:
            $message = '存在しないページです';
            break;
        case 408:
            $message = 'タイムアウトです';
            break;
        case 414:
            $message = 'リクエストURIが長すぎます';
            break;
        case 419:
            $message = '不正なリクエストです';
            break;
        case 500:
            $message = 'Internal Server Error';
            break;
        case 503:
            $message = 'Service Unavailable';
            break;
        default:
            $message = 'エラー';
            break;
    }
}
@endphp
<h1>{{ $status_code }} {{ $message }}</h1>
</body>
</html>
```

・・・

ちなみに実はこれ悩んだ経緯がありまして。 `abort()` 使ってエラー吐き出すと status code が思うように取れなかったんです。

```php
{{ http_response_code() }}
```

も

```php
@php $guzzle = new GuzzleHttp\Psr7\Response; echo($guzzle->getStatusCode()); @endphp
```

も200でしか返って来なかったのです。
つまりbladeのレンダリングするタイミングではこれらは200でとり扱ってるからなんですね。
どうやったら取れるかたどったところ、このように `$exception` で取れることを見つけたのでした。

<br>

----

<br>


いじょーでした! 
enjoy! Laravel!!

<br>

<div><a href="http://twitter.com/M_Ishikawa"><img src="http://api.osae.me/retwipi/M_Ishikawa"> @M_Ishikawa</a></div>

----

改定

(2019-05-18) Laravel5.3以降から`@php`が使えるようになったので変更しました。5.2以前は`@php`は`<?php`, `@endphp`は`?>`に読み替えてください。あと`use`端折ってたのですがコードがコピペで動くように改良しました。