【Laravel】カスタムExceptionクラスの作成とhandler周りの拡張

Laravel6.x以降であれば使える手法かと思います。（6.x未満では未確認）

# やりたいこと
画面遷移でエラーした時、モーダルにメッセージ出したい。

## 前提事項

1. 各箇所毎にモーダルのタイトルやメッセージは違ってくる。
2. モーダルを表示する手法は既存ソース内で確立済み。しかし、ヒジョーに微妙な気がする。

※現状の手法
　各コントローラーやサービスにて、モーダル用のタイトルやメッセージを設定。
　↓
　**各箇所でリダイレクト実施。　（うーんなんだかなぁ。。）**
　↓
　各ビューで出力。


# 結論（最終的な落とし所）
* モーダル用のExceptionクラスを作成
* 例外処理も同クラスに定義　（Handlerクラスではなく）
* **コントローラーやサービスは同Exceptionにモーダル情報を設定して投げるだけ　（スッキリ！）**
* ビューは渡されてきた情報を元にモーダル描画

<br>
それでは、具体的な内容に入っていきます。

# まず考えたこと、、
画面毎にモーダルのタイトルやメッセージは違うから、
やっぱりコントローラーでやるしかないかなぁ～
↓
でもコントローラーで処理するとFat Controllerになるしなー。。
それは避けたい。
↓
Laravelの思想的にエラーはHandlerで処理するから、
そっちですればええやん！

# やったこと
とりあえず、モーダル用のExceptionクラスを作りました。

```php:ModalException 
<?php

namespace App\Exceptions;

use Exception;

/**
 * モーダル表示する形式のエラーであることを示す例外
 */
class ModalException extends Exception
{
    // モーダル タイトル
    public $title;
    // モーダル メッセージ
    public $msg;
    // リダイレクト先
    public $redirectTo;

    /**
     * コンストラクタ
     * 
     * @param array $title タイトル
     * @param array $msg メッセージ
     * @param array $redirectTo リダイレクト先
     */
    public function __construct(string $title, string $msg, string $redirectTo = null)
    {
        $this->title= $title;
        $this->msg = $msg;
        $this->redirectTo = $redirectTo;

        parent::__construct($msg);
    }
}
```

Exceptionクラスできたぞ！
さぁ、あとはhandlerクラスのrenderメソッドかprepareResponseメソッドをオーバーライドして、
条件分岐を書いていきまｓ。。
↓
と、ここでExceptionクラスにrenderメソッドを定義すれば、
hadlerクラスがそちらをコールしてくれる、ということを知る。
（`laravel Reportable／Renderable例外`等でググると出てきます。）
>[エラー処理 8.x Laravel](https://readouble.com/laravel/8.x/ja/errors.html)
> Reportable／Renderable例外
>例外ハンドラのregisterメソッドで例外を型チェックする代わりに、カスタム例外に直接reportメソッドとrenderメソッドを定義することもできます。これらのメソッドが存在する場合、フレームワークによって自動的に呼び出されます。


```php:ModalException 
class ModalException extends Exception
{
    // 省略...

    // 以下を追加
    /**
     * 例外をHTTPレスポンスへ変換
     *
     * @param  \Illuminate\Http\Request $req
     * @return \Illuminate\Http\Response
     */
    public function render($req)
    {
        return $this->prepareResponse();
    }

    /**
     * レスポンスの準備
     *
     * @return \Illuminate\Http\Response
     */
    private function prepareResponse()
    {
        // 指定画面にモーダル用メッセージを受け渡しリダイレクトする
        return
            // リダイレクト先が設定されていなかったら、直前の画面にリダイレクト
            redirect($this->redirectTo ?? url()->previous())
            // 画面再描画用にリクエスト内容はセッションに保存しておく
            ->withInput()
            // モーダルタイトル、メッセージ
            ->with([
                'modalTitle' => $this->title,
                'modalMsg' => $this->msg,
            ]);
    }
}
```

これで仕組みは完成しました。

後は、コントローラーやサービス等々のモーダルエラーを出したいところでこのExceptionを投げます。


```php:HogeService
<?php
namespace App\Services;

use App\Exceptions\ModalException;
use App\Models\Data;

class HogeService
{
    /**
     * 指定したidのデータを取得する
     * @param int $id ID
     * @return Data $result データ情報
     */
    public function findData($id)
    {
        $result = Data::find($id);

        // 取得結果が0件だったらモーダルExceptionをスロー
        if (count($result) <= 0) {
            throw new ModalException('データ取得エラー', 'データが0件でした。');
        }

        return $result;
    }
}
```

スローするとhandler経由で先程のrenderメソッドが呼び出されて、
リダイレクトが行われ、ビュー側にモーダル用の情報が渡されます。

※ビューのコードは割愛します。


# まとめ
１．やはり、エラーハンドリングはhandler周りに任せた方がいいんだなーと改めて思った。
 
２．handlerクラスをFatにさせない仕組みがあることを知れた。

３．モーダルエラーだけでなく、別方式のエラーが出てきてもこの形で流用できそうなため、
　　このやり方は結構便利かも？と思った。

もっと良い方式や改善点等がありましたらご教授いただけると幸いです。