Larave Precognition で少し遊んでみる
前書き
ということで、このやや謎めいた新機能の Laravel Precognition で少し遊んでみました。
Laravel ver.9.33 から利用可能です。
[9.x] Introduce Laravel Precognition #44339
基本的には、バックエンド側の機能ですが、この機能をフロント側から楽に呼び出せるというライブラリが、用意されていますが、この記事を執筆現在は、まだリリースされていないようです。なおこの記事は、上記のプルリクや執筆現在、まだ正式公開ではないドキュメントを参考にしています。
で、この機能を使うと何ができて、何が嬉しいの?
precognition
を辞書で調べると、「〔将来の出来事などの〕予知」などと出てきます。この Precognition 機能は、何かの本処理前の「ちょっとした事前チェック」を行いたい時に、役に立つと言えます。
例えば、、、
- ユーザー登録フォームで、「メールアドレス」については、入力を終えた時点で、既にそのメールアドレスが登録がされていれば、その旨エラーをすぐに表示させたい。(リアルタイムバリデーション)
- 先着100名に達してしまったのに、まだフォームを入力している人がいる場合、その人達に「もう締め切りましたよ!」と、お知らせしたい。
- Aさんが編集中の記事をBさんが編集した時、Aさんが更新する前に「他の人が先に更新しました!」的なメッセージを表示させたい。
とか、色々あるかと思います。
上記で「事前チェック」と書きましたが、特にバリデーションで役立つと思いますし、実際、この機能は、バリデーション(Form Request)を意識して作られています。
で、この Precognition 機能を使わなくても、上記のような機能は実装できますが、この Precognition を使う事によって、今までなら事前チェックする為だけにURL(エンドポイント)を1つ追加していた所を、それを無くし、本体側の処理のURLと統一させることができるのですね。
例)いままで
- メールアドレスの重複チェックする用URL
- ユーザー登録する用のURL
例)Precognition 使うと
- ユーザー登録する用のURLのみ
スッキリしますね。バリデーションルールを2重に持たなくて済んだりします。
ざっくり仕組みは?
クライアントからリクエスト送る際に、Precognition: true というヘッダーを付けます。これが、Precognition 通信の合図です。で、Laravel 側は、Precognition 通信の時は、コントローラのメソッドの中身は、一切処理しません。ただ、そこに至るまでのミドルウェアやフォームリクエストは処理されます(コントローラのメソッドに渡されたパラメータの依存関係は解決される)。
で、バリデーションエラーなどが一切なく、無事コントローラの中身まで到達しようとしたら、コントローラの中身が実行されるのではなく、代わりに「204 No Content」のステータスコードが返されます。クライアント側は、この「204 No Content」を受け取って、「問題無かったな」という事が分かります。
もし、バリデーションでエラーになった際は、ある意味、今まで通りの処理になります(エラー情報が返る)。
フォームリクエストだけ?
フォームリクエストが主に使われそうですが、それに限らず、ミドルウェアで何かのチェック機能を仕込み、何か問題がある際は、そのミドルウェアで特定のレスポンスを返すこともできますので、可能性が広がりそうです。
ざっくり試してみました。
ということで、ざっくり試してみました。(やや手抜き感もありますが、ご了承下さい🙇♂️)
ユーザー登録フォームがあり、メールアドレスだけを重複チェックを含めたリアルタイムバリデーションをするものを作ります。
まずは、リアルタイムバリデーションを除いた部分は、以下の感じとなります。
<?php
use App\Http\Requests\UserSaveRequest;
use App\Models\User;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
return view('welcome');
});
Route::post('/', function (UserSaveRequest $request) {
// 実際は登録処理等する
return back()->with('status', '登録しました');
});
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rules\Password;
class UserSaveRequest extends FormRequest
{
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
return true; // true へ
}
/**
* Get the validation rules that apply to the request.
*
* @return array<string, mixed>
*/
public function rules()
{
return [
'name' => ['required', 'string', 'max:255'],
'email' => ['required', 'string', 'email:filter', 'unique:users'],
'password' => ['required', Password::defaults()],
];
}
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>タイトル</title>
</head>
<body>
<h2>エラー内容</h2>
@if ($errors->any())
<ul>
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<h3>{{ session('status') }}</h3>
<form method="post">
@csrf
名前:<input type="text" name="name" value="{{ old('name') }}">
<br>
メールアドレス:<input type="text" name="email" value="{{ old('email') }}">
<br>
パスワード:<input type="password" name="password" value="">
<input type="submit" value="送信する">
</form>
</body>
</html>
さて、では上記を Precognition を使って、メールアドレス欄だけリアルタイムバリデーションにしてみます。本来、Laravel 側で Precognition 用のライブラリが用意されるのですが、執筆時点まだ非公開のようなので、伝家の宝刀 jQuery を使って書いてみます🔥
メールアドレス欄に onblur を仕掛けて、下部にJSを追加します。
メールアドレス:<input type="text" name="email" value="{{ old('email') }}"
onblur="postThisForm(this.form)">
(略)
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.1/jquery.min.js"></script>
<script>
function postThisForm(form) {
let data = $(form).serializeArray(); // form のデータを収集
let options = {
url: "/",
data: data,
method: "post",
headers: {
"Precognition": "true",
"Precognition-Validate-Only": "email"
},
};
$.ajax(options).
done(function (data, textStatus, jqXHR) {
// 成功(204 No Content)の場合、こちらに来る
// エラー表示を消したり等する
}).fail(function (jqXHR) {
if (jqXHR.status === 422) { // バリデーションエラーの時
// 実際はエラーを画面に表示したりする
console.log(
JSON.parse(jqXHR.responseText).errors
);
}
});
return false;
}
</script>
コードの細かい説明は省略しますが、ポイントは、headers です。"Precognition": "true", で、Precognition の合図を送り、"Precognition-Validate-Only": "email" で、バリデーションさせたい項目を送ります。(この指定が無いと全項目バリデーションします。複数ある時はカンマ区切り)
上記では手抜きをして、名前やパスワードも送ってしまっていますが、メールアドレスしかバリデーションしないので、本来、名前やパスワードは送る必要ないですね。
次は、Laravel 側を対応させます。先程の web.php の Post 側の方に、HandlePrecognitiveRequests ミドルウェアを仕掛けます。
use Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests;
(略)
Route::post('/', function (UserSaveRequest $request) {
(略)
})->middleware(HandlePrecognitiveRequests::class);
以上です。
これで、メールアドレス欄を入力して、フォーカスが外れると、メールアドレス欄だけリアルタイムバリデーションが掛かる事になります。
簡単ですね。
雑感
最初、Precognition を見た時は、「意味不明だな…」と思いましたが、正体が分かってくると、意外とシンプルな感じで扱い易そうです。
他にもドキュメントには色々書かれていますので、是非公開されたら参照してみて下さい。
間違い等ありましたら、コメント下さい。
Discussion