Laravelの404ページやその他の例外ページの作成方法
こんにちは。Nonです。
今回は備忘録としてLaravelでの404ページなどのカスタマイズ方法について書いていきたいと思います。
ページ用のbladeを作成する
普通にbladeとして作成します。
下記の例ではh1タグのみにしていますが、CSSなどを利用してページを装飾することをおすすめします。
<h1>404 Not Found. このページは存在しません。</h1>
<a href="/" class="btn btn-primary">TOPへ戻る</a>
404というか例外時の処理
Laravelはroutesに無い時NotFoundHttpExceptionをスローします。
このスローの結果、最終的に/app/Exceptions/Handler.phpのrenderメソッドへ処理が移ります。
また、自分のアプリケーションで独自の例外を作成している場合もアプリケーション層でキャッチしていない場合、ここへ処理が来ます。
/**
 * Render an exception into an HTTP response.
 *
 * @param \Illuminate\Http\Request $request
 * @param \Exception $exception
 * @return \Illuminate\Http\Response
 */
public function render($request, Exception $exception)
{
    return parent::render($request, $exception);
}
ここのrenderメソッドを編集します。
ページがないときの例外
/**
 * Render an exception into an HTTP response.
 *
 * @param \Illuminate\Http\Request $request
 * @param \Exception $exception
 * @return \Illuminate\Http\Response
 */
public function render($request, Exception $exception)
{
    if ($exception instanceof NotFoundHttpException) {
        return view('pages.errors.404');
    }
    return parent::render($request, $exception);
}
サーバーエラーが発生したときの例外
他の例外時、例えばサーバーエラーを示す500エラーなどの場合は、独自の例外や、Laravelの例外すべてを拾いたいので、このようにするのがいいかもしれません。
/**
 * Render an exception into an HTTP response.
 *
 * @param \Illuminate\Http\Request $request
 * @param \Exception $exception
 * @return \Illuminate\Http\Response
 */
public function render($request, Exception $exception)
{
    if ($exception instanceof NotFoundHttpException) {
        return view('pages.errors.404');
    }
    
    // 一番最後に書くことを推奨
    if ($exception instanceof Throwable) {
        return view('pages.errors.500');
    }
    return parent::render($request, $exception);
}
ログインに失敗した時や、権限のエラーのときの例外
ログイン時は401 Unauthorizedで、権限エラーは403 Forbiddenです。
しかし、このエラーをここで拾うのはちょっとおかしいかもしれません。
このメソッドはアプリケーションが想定していない入力をされた時に発生する例外をまとめるべきです。
ログインエラーや、アプリケーション権限の例外は想定できる例外ですので、ControllerやMiddlewareレベルでtry-catchを使用するべきです。
例えばログインが必要な機能ページの例外はLoggedInMiddlewareなどを作成して自身のログインチェック時にスローされる例外をキャッチしてエラーページをレンダリングするほうが、/app/Exceptions/Handler.phpのコードを汚さなくてすみます。
/**
 * Handle an incoming request.
 *
 * @param  \Illuminate\Http\Request  $request
 * @param  \Closure  $next
 * @param  string|null  $guard
 * @return mixed
 */
public function handle($request, Closure $next, $guard = null)
{
    try {
         // ログインチェック
    } catch (UnauthorizedHttpException $e) {
        // 認証のエラーページやリダイレクト処理
    }
    return $next($request);
}
ちなみにLaravelの標準で付属している認証システムを利用している場合は、すでにRedirectIfAuthenticatedが実装されているので、不要です。
アプリケーションの権限などの場合はログインと同様にRoleMiddlewareなどを作成し、権限が関係するルーティングにこのミドルウェアを適用し、try-catchを使います。
/**
 * Handle an incoming request.
 *
 * @param  \Illuminate\Http\Request  $request
 * @param  \Closure  $next
 * @param  string|null  $guard
 * @return mixed
 */
public function handle($request, Closure $next, $guard = null)
{
    try {
         // 権限チェック
    } catch (RoleException $e) {
        // 権限のエラーページやリダイレクト処理
    }
    return $next($request);
}
APIのときとかは?
LaravelのRequestクラスにはexpectsJsonメソッドが実装されているので、これを利用してAPIでコールされているかどうかを確認しましょう。(APIがXMLとかの場合は他の関数を用意して利用しましょう)
/**
 * Render an exception into an HTTP response.
 *
 * @param \Illuminate\Http\Request $request
 * @param \Exception $exception
 * @return \Illuminate\Http\Response
 */
public function render($request, Exception $exception)
{
    if ($exception instanceof NotFoundHttpException) {
        if ($request->expectsJson()) {
            response()->json([
                'message' => 'Not Found.',
            ]);
        }
        return view('pages.errors.404');
    }
    
    // 一番最後に書くことを推奨
    if ($exception instanceof Throwable) {
        if ($request->expectsJson()) {
            response()->json([
                'message' => 'Server Error.',
            ]);
        }
        return view('pages.errors.500');
    }
    return parent::render($request, $exception);
}
基本的に/app/Exceptions/Handler.phpはあまり利用しないようにしたほうがいいかも
ここに例外がスローされる時はアプリケーション上本当に拾えない例外か、よっぽどの想定外(PHP構文エラーや実装時のバグとか)を拾うためのメソッドです。
なので、アプリケーション上で開発時に想定できる例外はキチンと他の層でキャッチしたほうが可読性が上がります。
最後に
今回は備忘録とエラーハンドリングの整理がてらまとめてみました。
最初は404エラーについてにまとめようと思っていましたが、結局それはエラーハンドリングをどのような構造で作成するかということでしたので、他の例外についてもまとめてみました。
正常時の設計も大事で、こちらばかりに注目が行きがちですが、エラーハンドリングをしっかりすることもとても重要です。例外を自分の想定どおりにハンドリングできていないと、運用のときどこでエラーが起きたとかを拾いづらくなってとても困ります。
一度適当な記事を書いてしまったので、リライトしました。
これからもLaravelを利用したアプリの設計について思うところがあれば記事を更新したいと思います。
その時はよしなに。
.
 
    