Laravel5.3、エラーページの表示

タグ: Laravel5.3  

多くの初心者はなんとドキュメントを読みません。別にLaravelに限った話でもありませんね。

そこで、久しぶりにエラーページの取り扱いについて書こうと思います。「タグ」にとどまらず、「タイトル」にも含めていますが、それを私のように読まない人もいますので、あえて書きましょう。バージョン5.3です。(あえて書くのは重要だからです。)

今回、自分の書いた記事を見直し、記憶を遡りました。4.0のベータ、リリース直後は、以下に記述したエラーページ表示の仕組みは、一時なくなっていました。4.0の後半か4.1で復活しました。

HTTPステータスコード

Laravelのエラービューはresources/views/errorsディレクトリ下に存在します。拡張子が.php.blade.phpのビューファイルで、名前がステータスコードのものが存在すれば、自動的に表示されます。

たとえば、URLに一致するルートが存在しない場合は、Laravelは内部的に404のステータスコード例外を生成させます。もし、resources/views/errors/404.phpresources/views/errors/404.blade.phpビューファイルが存在したら、それが自動的に表示される仕組みになっています。

ステータスコードに対応するビューファイルが存在していない場合、Symfonyの味気ないエラーページが表示されます。(このページだけを見て、「これはLaravelでできている」と判断されている方が多いようですが、表示されるエラーページだけでは、どのフレームワークを使用しているかは判定できません。Laravelと同じSymfonyのコンポーネントを使用しているライブラリやフレームワークであれば、同じメッセージが標準で表示されるでしょう。)

そのため、自分のWebアプリで発生させるステータスコードに対応する、HTTPステータスを表すエラーページを全部用意しておきましょう。

エラーページのビューファイルは、通常のビューとまったく一緒です。Bladeテンプレートを使用するならば、@extendsディレクティブで親レイアウトを呼び出すこともできます。

例外に含めたメッセージの取得

Laravelではどこからでもabortヘルパーを使用し、HTTPステータスの例外を発生できます。例外を発生させ、それをキャッチしなければ、例外ハンドラで捉えられます。

abort(500);

// メッセージ付き
abort(500, 'エラーの理由');

例外ハンドラの中で、このHTTPステータス例外に対し処理を行わなければ、前記の通り自動的に、ステータス例外を示すビューが表示されます。今回の場合、500と言うファイル名のビューが表示されるわけです。

ちなみに通常例外はPHPのExceptionクラスを拡張しています。abortで指定するメッセージはベースクラスに用意されているmessageプロパティとして保存されます。そのため、ゲッターとしてgetMessageメソッドが継承されています。

この時、エラービューの中には、$exception変数が用意されます。つまり、特別なことを行わずとも、abortヘルパーに渡したメッセージを表示できます。

ビューファイルの中:

<?php echo $exception->getMessage(); ?>

Bladeを使用する場合は:

{{ $exception->getMessage() }}

ただし、当然のことながら、あまりに詳しい内容をユーザーへ表示するのは、脆弱性につながります。また、ユーザーがテクニカルな人間とは限らないわけですから、技術的なことを表示するよりも、「どのように対処してもらいたいか」を決め打ちのエラービューで表示する程度で通常はよろしいかと思います。

詳しい内容は、ビューで表示するよりもログに残し、トラブル時の判断、対処がしやすくするほうが賢明です。そのため、通常のWebアプリケーションでしたら、ビューにわざわざ例外の内容を渡す必要が少ないかと思います。

HTTP例外以外の例外の取り扱い

今までのLaravelコア開発経緯からお話しますと、例外に関しては次のように開発が進みがちです。

  1. 新しい例外、もしくは例外が変更される。(デフォルトの例外処置なし)
  2. デフォルトの処置が追加され、デフォルトで適当なHTTPステータスとして処理される。

1と2が同時に行われるのが理想的ですが、ときより時間が開くことがあります。特に、メジャーリリース直後に起きます。適当なデフォルト処置がまだ追加されていない場合に、その例外が自分のシステムで発生する可能性がある場合、自前で処理する必要が起きます。

また、Laravelのデフォルトの例外処理、たとえば名前空間は省略しますが、ModelNotFoundExceptionを404ステータスエラー、AuthorizationExceptionを403ステータスエラーにするのを変更したい場合などにも、各例外をキャッチし、それぞれに処理を行う必要があります。

この記事は5.2向きですが、5.3でも同様です。(文章中のサンプルが、HTTPステータスエラーを別のステータスエラーに変更しているものが多いですが、実際にはabortメソッドの呼び出し側で適切な値を設定すれば、このようなHTTPステータスの変換は必要性が低くなります。むしろ、HTTPステータスに関しては、例外ハンドラ内で変換するよりも、初めからabortヘルパーに適切な値を指定することをおすすめします。そのほうが素直です。)

(もし仮に、ハンドラでステータスコードをわざわざ変換するような事態が起きているなら、それはとても特殊な状況なのですから、それを表す例外クラスを定義し、その独自例外を投げるべきです。そしてハンドラでは、自分の独自例外を適切に処理するほうが、素直でわかりやすい実装です。私も「こうしたことができるよ」という例として示すことはあります。ただ、実用的ではありませんし、独自例外を実装するほうが好みです。)

ただし、AuthenticationExceptionに関しては、5.3から例外ハンドラーのunauthenticatedメソッドで処理を記述していますので、このメソッド内のロジックを必要に応じ変更してください。(例外自身も5.3からこの例外に変更されています。)

ちなみに、例の5.1向け書籍のサンプルコードとして、以下のような内容を紹介しました。エラーページの表示を行うrenderメソッドの中身です。

if ($e instanceof TokenMismatchException) {
   abort(403);
}

if ($e instanceof ModelNotFoundException) {
   $e = new NotFoundHttpException($e->getMessage(), $e);

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

コメントは外しています。多分5.2からですが、abortヘルパーがハンドラー内で使用できなくなりました。レスポンスを直接生成、もしくはレスポンスへ変換される文字列やビューインスタンスを返します。

もしくは、新しくHTTPステータス例外クラスインスタンスを生成させ、それを親へ渡してやるほうが良いかもしれません。大した違いはありませんが、わざわざ例外を表示するためにビューへ渡す手間は省けます。

新しく生成した例外を親のrenderメソッドに処理させる場合は、前述の通り、ビューへ例外が$exceptionとして自動的に渡されます。以下は親に404ステータスの例外を処理させるサンプルコードです。

use Exception;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
...
    public function render($request, Exception $exception)
    {
        if ($exception instanceof ModelNotFoundException) {
            $exception = new NotFoundHttpExceptionon($exception->getMessage(),
                $exception);
        }

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

ModelNotFoundExceptionはEloquentのfindOrFailfirstOrFailメソッドで、指定したIDのレコードが見つからない場合に発生する例外です。通常、両方のメソッドの引数として指定されるID値は、URL中に指定されているレコード番号ですので、それに対するレコードが見つからない場合、存在しないURLを指定したとみなせます。そこでNotFoundHttpExceptionon例外、これはステータスコード404を表す例外ですので、これに置き換えています。

実際のレンダー処理はparentに任せています。404ステータス例外のデフォルト処理としてerrors.404のビューがレンダーされます。例外自身も前述の通り、ビューへ$exceptionとして渡されます。

同様に、開発するシステムで発生する他の例外についても、適当なHTTPエラー例外へ置き換えましょう。それで、エラーページに関しては、実用的なシステムになります。

より詳しい、例外の処理については、他に記事を書かれている方がいらっしゃいます。

公式ドキュメント(英語)、日本語翻訳ページ、そして必要であればソースコードを参照してください。

(追記:ページ中程、リンク先のコメントにて「自分で書け」と言われたので、書きました。コードを確認していない部分もありますが、ある程度は確認済みです。5.3もメンテ期間はあと一年ありませんのが、新しい情報のほうがよろしいかと思い、5.3ベースで紹介しました。)

(追記:コメントを書かれるのが嫌なら、このようにコメント機能がない自分のブログなどを利用しましょう。ちなみに、以前はSEOに有利だと言うことでコメント機能を用意しました。その後、ゴミコメントやポストを管理している側の責任などが問われるようになり、またさほどSEOに有利に働かないという風潮になったため、機能は削除しました。)