👓

Larave Precognition で少し遊んでみる

2022/10/05に公開

前書き

ということで、このやや謎めいた新機能の 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と統一させることができるのですね。

例)いままで

  1. メールアドレスの重複チェックする用URL
  2. ユーザー登録する用のURL

例)Precognition 使うと

  1. ユーザー登録する用のURLのみ

スッキリしますね。バリデーションルールを2重に持たなくて済んだりします。

ざっくり仕組みは?

クライアントからリクエスト送る際に、Precognition: true というヘッダーを付けます。これが、Precognition 通信の合図です。で、Laravel 側は、Precognition 通信の時は、コントローラのメソッドの中身は、一切処理しません。ただ、そこに至るまでのミドルウェアやフォームリクエストは処理されます(コントローラのメソッドに渡されたパラメータの依存関係は解決される)。

で、バリデーションエラーなどが一切なく、無事コントローラの中身まで到達しようとしたら、コントローラの中身が実行されるのではなく、代わりに「204 No Content」のステータスコードが返されます。クライアント側は、この「204 No Content」を受け取って、「問題無かったな」という事が分かります。
もし、バリデーションでエラーになった際は、ある意味、今まで通りの処理になります(エラー情報が返る)。

フォームリクエストだけ?

フォームリクエストが主に使われそうですが、それに限らず、ミドルウェアで何かのチェック機能を仕込み、何か問題がある際は、そのミドルウェアで特定のレスポンスを返すこともできますので、可能性が広がりそうです。

ざっくり試してみました。

ということで、ざっくり試してみました。(やや手抜き感もありますが、ご了承下さい🙇‍♂️)

ユーザー登録フォームがあり、メールアドレスだけを重複チェックを含めたリアルタイムバリデーションをするものを作ります。

まずは、リアルタイムバリデーションを除いた部分は、以下の感じとなります。

web.php
<?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', '登録しました');
});
UserSaveRequest.php
<?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()],
        ];
    }
}
welcome.php
<!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 ミドルウェアを仕掛けます。

web.php
use Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests;
(略)

Route::post('/', function (UserSaveRequest $request) {
(略)
})->middleware(HandlePrecognitiveRequests::class);

以上です。

これで、メールアドレス欄を入力して、フォーカスが外れると、メールアドレス欄だけリアルタイムバリデーションが掛かる事になります。
簡単ですね。

雑感

最初、Precognition を見た時は、「意味不明だな…」と思いましたが、正体が分かってくると、意外とシンプルな感じで扱い易そうです。

他にもドキュメントには色々書かれていますので、是非公開されたら参照してみて下さい。

間違い等ありましたら、コメント下さい。

Discussion