Edited at

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

2015 Laravel Advent Calendar 14日目! Laravel大好き @M_Ishikawa です。

さて、1日目の記事ではエラーログについての内容でしたが、今回もエラーに関する内容です。

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

それらは

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
...

うーん。

1つのテンプレートファイルで済ませたい!!

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

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

結論を急ぐ方は ここ

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




まず、エラーページをレンダリングするのは app/Exceptions/Handler.php の render() です。


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()


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()


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 にしたいので


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に直書きしてみます。


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 が思うように取れなかったんです。

{{ http_response_code() }}

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

も200でしか返って来なかったのです。

つまりbladeのレンダリングするタイミングではこれらは200でとり扱ってるからなんですね。

どうやったら取れるかたどったところ、このように $exception で取れることを見つけたのでした。




いじょーでした!

enjoy! Laravel!!



改定

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