Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
126
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

@M_Ishikawa

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端折ってたのですがコードがコピペで動くように改良しました。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
126
Help us understand the problem. What are the problem?