Laravelでエラー時に表示するページのカスタマイズ

  • 5
    いいね
  • 3
    コメント

やりたいこと

  • システムエラー時は500のページを表示させたい。
  • 存在しないURLにアクセスされた時は404のページを表示させたい。
  • その他各エラー処理で適宜403や404のページを表示させたい。
  • ↑の時にそれぞれ表示させるメッセージを変えたい。

エラー発生時の遷移先を変更

app\Exceptions\Handler.phpを修正します。
キャッチしたエラーを判定して、どのレスポンスを返すのかをrenderメソッドに記述します。

Hanlder.php
public function render($request, Exception $e) {
    return parent::render($request, $e);
}

ここの処理をよしなに変える。

Step1:HTTPのステータスコードで判定する処理を加える。

Handler.php
public function render($request, Exception $e) {
    if($this->isHttpException($e)) {
        // 403
        if($e->getStatusCode() == 403) {
            return response()->view('errors.403');
        }
        // 404
        if($e->getStatusCode() == 404) {
            return response()->view('errors.404');
        }
        // 500
        return response()->view('errors.500');
    } 
    return parent::render($request, $e);
}

$e->getStatusCode()でステータスコードが取れるので、その値によって返すビューを変える。ビューはresources\views\errorsに置く。
一つ注意しておきたいのが、$eがHttpExceptionでない場合はgetStatusCodeで落ちるので$this->isHttpException($e))しておくこと。

Step2:任意のステータスコードでHttpExceptionを発生させる。

abort('ステータスコード')という便利な関数がある。

(よくある例)
ユーザーごとのプロフィールページに以下のURIでアクセスできる。
/user/profile/23
たぶんコントローラーはこんな感じ。

UserController
public function getUserProfile($id) {
    $user = User::find($id);
    return view('user.profile', ['user' => $user]);
}

コントローラーの中でEloquent使うのやめようとかいう話は置いといて。。。
これ$userがnullの時まずいっすよね。
abort('404')を使って、存在しないIDが入力された時は404ページにご案内します。

UserController
public function getUserProfile($id) {
    $user = User::find($id);
    if($user == null) {
        abort('404');
    }
    return view('user.profile', ['user' => $user]);
}

これでさっきのHandler.php#renderに記述したステータスコードが404の時の処理が呼ばれる。

Step3:エラーページに表示する文言を場合に応じて変える。

abort()の第2引数には文字列を渡せます。

UserController.php
public function getUserProfile($id) {
    if(~) {
        abort('403', '権限がありません。')
    } else if(~) {
        abort('403', '無効なURLです。')
    } else if(~) {
        abort('403', 'アクセスが拒否されました。')
    }
    return view('user.profile', ['user' => $user]);
}

渡した文字列はgetMessage()で取り出すことができます。

Handler.php
public function render($request, Exception $e) {
    if($this->isHttpException($e)) {
        // 403
        if($e->getStatusCode() == 403) {
            return response()->view('errors.403', ['message' => $e->getMessage()]);
        }
        // 404
        if($e->getStatusCode() == 404) {
            return response()->view('errors.404');
        }
        // 500
        return response()->view('errors.500');
    } 
    return parent::render($request, $e); 
}

ビューの方での表示の仕方はこう。

403.blade.php
<h1>403 Forbidden</h1>
<p>{{ $message }}</p>

Step4:バリデーションエラーの回避

LaravelではデフォルトでSymphonyのエラー画面が出ます。
本番でその画面が表示されるようではまずいので、明示的に制御しないその他のエラーについては
デフォルトですべて500ページを表示させたいと思います。

Handler.php
public function render($request, Exception $e) {
    if($this->isHttpException($e)) {
        // 403
        if($e->getStatusCode() == 403) {
            return response()->view('errors.403', ['message' => $e->getMessage()]);
        }
        // 404
        if($e->getStatusCode() == 404) {
            return response()->view('errors.404');
        }
    } 
    return response()->view('errors.500');
}

こう書き直したところ予期せぬ不具合が。
バリデーションにフォームリクエストを使っていたのですが、バリデーションエラーになった時にも500ページが。

フォームリクエストでバリデーションに失敗した場合、HttpResponseExecptionというものが返ってくる。
こいつをさばいてやればいいだけの話だった。

Handler.php
public function render($request, Exception $e) {

    // 例外のクラスで判定する
    if($e instanceof HttpResponseException) {
        return parent::render($request, $e);
    }

    if($this->isHttpException($e)) {
        // 403
        if($e->getStatusCode() == 403) {
            return response()->view('errors.403', ['message' => $e->getMessage()]);
        }
        // 404
        if($e->getStatusCode() == 404) {
            return response()->view('errors.404');
        }
    } 
    return response()->view('errors.500');
}

独自例外なんかを定義した場合も同じような書き方で個別に対応できます。
排他制御の例外とかこれで書きました↓↓↓
Laravelで独自例外処理を実装する(楽観的排他制御andトランザクション処理)

(おまけ)ログの設定

1日1ファイルでログを取っておく設定

config\app.php
// dailyにする
'log' => env('APP_LOG', 'daily'),
// dailyなので10日分まで保存
'log_max_files' => '10',