2024年10月30日
Livewireのエラー処理について理解していないところが多かったので。
Livewireに対する通常のレスポンス
Livewireは通常、非同期でサーバーと通信をする。
つまり、ブラウザのバックグラウンドでJavaScriptがサーバーとHTTP通信をしているということになる。
サーバーからのHTTPレスポンスを見てみると
{
"components": [
{
"snapshot": ...
"effects": {
"returns": [
null
],
"html": ...
}
}
],
"assets": []
}
のようにJSON形式になっている。
LivewireはこのJSON形式のレスポンスを取得し、再描画している。
Laravelのエラー
Laravel内でエラーが発生すると、専用のエラー画面が表示される。
あれってつまり、LaravelがLaravel内で発生したエラーをキャッチしてHTMLにしてHTTPレスポンスで返しているということになる。
そのため、$this->authorize('delete', $post);
とかでエラーになると、Livewire的には先のJSON形式のレスポンスを期待しているのに、HTML形式でレスポンスが返ってくることになる。
結果、 のようにiframeのモーダルでエラーが表示される。
これは、Livewireの粋な計らいで、HTML形式のレスポンスならiframeで埋め込んで表示するようになっているみたい。
簡易的な実装ならこれでもいいんだけど、ほとんどの開発者にとってはこの表示は避けたいはず。
このモーダルは前に私が投稿したちゃんとしたモーダルをLaravel+Alpine.js+TailwindCSSで作成するに反していて、ユーザーに優しくないし、iframeで1画面に2個同じサイトがあるのはちょっと異質。
Livewireのモーダルエラー表示をカスタマイズする
これ、一応カスタマイズできなくはない。
公式ドキュメントにある通り、Requestの結果をフックに処理が実行できて、preventDefault()
を付けることでモーダル表示も無くなる。
つまり、
document.addEventListener('livewire:init', () => {
Livewire.hook('request', ({ fail }) => {
fail(({ status, preventDefault }) => {
if (status === 403) {
alert('この処理は許可されていません。')
preventDefault()
}
})
})
})
のようにしてあげると みたいに任意の処理ができる。
JSON形式のエラーで独自のエラーメッセージを返す
先の方法でも大体は問題ないと思うんだけど、JSON形式でエラーを返してあげると更にカスタマイズの幅が広がる。
JSON形式でエラーを返す方法は色々あるだろうけど、ここではカスタム例外を作る方法を紹介する。
先ず、オリジナルの例外を作る
php artisan make:exception Custom403LivewireException
中身は以下のような感じにしてみる
<?php
namespace App\Exceptions;
use Exception;
class Custom403LivewireException extends Exception
{
public function render($request)
{
if ($request->hasHeader('x-livewire')) {
return response()->json([
"message" => $this->getMessage()
], 403);
} else {
abort(403);
}
}
}
ここでは、リクエストのHeaderにx-livewireが付いているなら、JSON形式でメッセージを返す感じ。
次に認可処理部分を
- $this->authorize('update', $this->post);
+ if (Gate::denies("update", $this->post)) {
+ throw new Custom403LivewireException("認証エラーだよ");
+ }
のようにカスタム例外を返す形に。
そして、さっきのjsを
<script>
document.addEventListener('livewire:init', () => {
Livewire.hook('request', ({ fail }) => {
fail(({ status, content, preventDefault }) => {
if (status === 403) {
preventDefault();
try {
jsonContent = JSON.parse(content);
errorMessage = jsonContent.message || "エラーが発生しました";
alert(errorMessage)
} catch (e) {
alert("エラーが発生しました");
}
}
})
})
})
</script>
のようにすれば、独自のエラーメッセージを表示できるようになった。
Livewireは非同期処理をしてるんだからJSON形式を期待するし、違ったら独自の処理をするっていうのが言われれば当たり前なんだけど、私はなかなか気づけなかった。