#はじめに
Laravel Precognition は、将来の HTTP リクエストの結果を予測できます。主な用途の一つは、バックエンドのバリデーションルールを重複させることなく、フロントエンドの JavaScript アプリケーションに「ライブ」バリデーションを提供できることです。Precognition は特に Laravel の Inertia ベースの スターターキット と相性が良いです。
Laravel が「予知リクエスト」を受け取ると、ルートのすべてのミドルウェアを実行し、ルートのコントローラー依存関係を解決します。これには フォームリクエスト のバリデーションも含まれますが、実際にルートのコントローラーメソッドは実行されません。
#ライブバリデーション
#Vue の使用
Laravel Precognition を使うと、フロントエンドの Vue アプリケーションでバリデーションルールを重複させることなく、ユーザーにライブバリデーション体験を提供できます。動作を示すために、ユーザー作成フォームを作成してみましょう。
まず、ルートで Precognition を有効にするには、HandlePrecognitiveRequests ミドルウェアをルート定義に追加します。また、ルートのバリデーションルールを格納するために フォームリクエスト を作成してください:
use App\Http\Requests\StoreUserRequest;
use Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests;
Route::post('/users', function (StoreUserRequest $request) {
// ...
})->middleware([HandlePrecognitiveRequests::class]);
次に、Vue 用の Laravel Precognition フロントエンドヘルパーを NPM でインストールしてください:
npm install laravel-precognition-vue
Laravel Precognition パッケージをインストールしたら、useForm 関数を使ってフォームオブジェクトを作成できます。HTTP メソッド(post)、ターゲット URL(/users)、初期フォームデータを渡します。
ライブバリデーションを有効にするには、各入力の change イベントでフォームの validate メソッドを呼び出し、入力名を渡します:
<script setup>
import { useForm } from 'laravel-precognition-vue';
const form = useForm('post', '/users', {
name: '',
email: '',
});
const submit = () => form.submit();
</script>
<template>
<form @submit.prevent="submit">
<label for="name">Name</label>
<input
id="name"
v-model="form.name"
@change="form.validate('name')"
/>
<div v-if="form.invalid('name')">
{{ form.errors.name }}
</div>
<label for="email">Email</label>
<input
id="email"
type="email"
v-model="form.email"
@change="form.validate('email')"
/>
<div v-if="form.invalid('email')">
{{ form.errors.email }}
</div>
<button :disabled="form.processing">
Create User
</button>
</form>
</template>
ユーザーがフォームに入力すると、Precognition はルートのフォームリクエストのバリデーションルールに基づくライブバリデーション結果を提供します。入力が変更されると、デバウンスされた「予知」バリデーションリクエストが Laravel アプリケーションに送信されます。デバウンスタイムアウトはフォームの setValidationTimeout 関数で設定できます:
form.setValidationTimeout(3000);
バリデーションリクエストが進行中の場合、フォームの validating プロパティは true になります:
<div v-if="form.validating">
Validating...
</div>
バリデーションリクエストやフォーム送信時に返されたエラーは、自動的にフォームの errors オブジェクトに反映されます:
<div v-if="form.invalid('email')">
{{ form.errors.email }}
</div>
フォームにエラーがあるかどうかは、フォームの hasErrors プロパティで判定できます:
<div v-if="form.hasErrors">
<!-- ... -->
</div>
また、入力がバリデーションに合格したか失敗したかは、フォームの valid と invalid 関数に入力名を渡して判定できます:
<span v-if="form.valid('email')">
✅
</span>
<span v-else-if="form.invalid('email')">
❌
</span>
フォーム入力は、変更されてバリデーションレスポンスを受け取った後でのみ、有効または無効として表示されます。
Precognition でフォームの一部の入力だけをバリデートする場合、エラーを手動でクリアすることが便利です。フォームの forgetError 関数を使って実行できます:
<input
id="avatar"
type="file"
@change="(e) => {
form.avatar = e.target.files[0]
form.forgetError('avatar')
}"
>
もちろん、フォーム送信のレスポンスに応じてコードを実行することもできます。フォームの submit 関数は Axios のリクエストプロミスを返します。これにより、レスポンスのペイロードにアクセスしたり、送信成功時にフォームをリセットしたり、失敗時の処理が簡単にできます:
const submit = () => form.submit()
.then(response => {
form.reset();
alert('User created.');
})
.catch(error => {
alert('An error occurred.');
});
フォーム送信リクエストが進行中かどうかは、フォームの processing プロパティで判定できます:
<button :disabled="form.processing">
Submit
</button>
#Vue と Inertia の使用
Vue と Inertia で Laravel アプリケーションを開発する際に手早く始めたい場合は、スターターキット の利用を検討してください。Laravel のスターターキットはバックエンドとフロントエンドの認証スキャフォールドを提供します。
Vue と Inertia で Precognition を使う前に、まず Vue での Precognition の使い方 を確認してください。Inertia と Vue を使う場合は、Inertia 対応の Precognition ライブラリを NPM でインストールする必要があります:
npm install laravel-precognition-vue-inertia
インストール後、Precognition の useForm 関数は、上記のバリデーション機能を備えた Inertia の フォームヘルパー を返します。
フォームヘルパーの submit メソッドは簡略化されており、HTTP メソッドや URL を指定する必要がありません。代わりに、Inertia の visit オプション を唯一の引数として渡せます。また、submit メソッドは Vue の例のように Promise を返しません。代わりに、submit に渡す visit オプションで Inertia がサポートする イベントコールバック を指定できます:
<script setup>
import { useForm } from 'laravel-precognition-vue-inertia';
const form = useForm('post', '/users', {
name: '',
email: '',
});
const submit = () => form.submit({
preserveScroll: true,
onSuccess: () => form.reset(),
});
</script>
#React の使用
Laravel Precognition を使うと、フロントエンドの React アプリケーションでバリデーションルールを重複させることなく、ユーザーにライブバリデーション体験を提供できます。動作を示すために、ユーザー作成フォームを作成してみましょう。
まず、ルートで Precognition を有効にするには、HandlePrecognitiveRequests ミドルウェアをルート定義に追加します。また、ルートのバリデーションルールを格納するために フォームリクエスト を作成してください:
use App\Http\Requests\StoreUserRequest;
use Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests;
Route::post('/users', function (StoreUserRequest $request) {
// ...
})->middleware([HandlePrecognitiveRequests::class]);
次に、React 用の Laravel Precognition フロントエンドヘルパーを NPM でインストールしてください:
npm install laravel-precognition-react
Laravel Precognition パッケージをインストールしたら、useForm 関数を使ってフォームオブジェクトを作成できます。HTTP メソッド(post)、ターゲット URL(/users)、初期フォームデータを渡します。
ライブバリデーションを有効にするには、各入力の change と blur イベントを監視します。change イベントハンドラーでは、setData 関数で入力名と新しい値を渡してフォームのデータを更新します。blur イベントハンドラーでは、フォームの validate メソッドを呼び出し、入力名を渡します:
import { useForm } from 'laravel-precognition-react';
export default function Form() {
const form = useForm('post', '/users', {
name: '',
email: '',
});
const submit = (e) => {
e.preventDefault();
form.submit();
};
return (
<form onSubmit={submit}>
<label for="name">Name</label>
<input
id="name"
value={form.data.name}
onChange={(e) => form.setData('name', e.target.value)}
onBlur={() => form.validate('name')}
/>
{form.invalid('name') && <div>{form.errors.name}</div>}
<label for="email">Email</label>
<input
id="email"
value={form.data.email}
onChange={(e) => form.setData('email', e.target.value)}
onBlur={() => form.validate('email')}
/>
{form.invalid('email') && <div>{form.errors.email}</div>}
<button disabled={form.processing}>
Create User
</button>
</form>
);
};
ユーザーがフォームに入力すると、Precognition はルートのフォームリクエストのバリデーションルールに基づくライブバリデーション結果を提供します。入力が変更されると、デバウンスされた「予知」バリデーションリクエストが Laravel アプリケーションに送信されます。デバウンスタイムアウトはフォームの setValidationTimeout 関数で設定できます:
form.setValidationTimeout(3000);
バリデーションリクエストが進行中の場合、フォームの validating プロパティは true になります:
{form.validating && <div>Validating...</div>}
バリデーションリクエストやフォーム送信時に返されたエラーは、自動的にフォームの errors オブジェクトに反映されます:
{form.invalid('email') && <div>{form.errors.email}</div>}
フォームにエラーがあるかどうかは、フォームの hasErrors プロパティで判定できます:
{form.hasErrors && <div><!-- ... --></div>}
また、入力がバリデーションに合格したか失敗したかは、フォームの valid と invalid 関数に入力名を渡して判定できます:
{form.valid('email') && <span>✅</span>}
{form.invalid('email') && <span>❌</span>}
フォーム入力は、変更されてバリデーションレスポンスを受け取った後でのみ、有効または無効として表示されます。
Precognition でフォームの一部の入力だけをバリデートする場合、エラーを手動でクリアすることが便利です。フォームの forgetError 関数を使って実行できます:
<input
id="avatar"
type="file"
onChange={(e) =>
form.setData('avatar', e.target.value);
form.forgetError('avatar');
}
>
もちろん、フォーム送信のレスポンスに応じてコードを実行することもできます。フォームの submit 関数は Axios のリクエストプロミスを返します。これにより、レスポンスのペイロードにアクセスしたり、送信成功時にフォームをリセットしたり、失敗時の処理が簡単にできます:
const submit = (e) => {
e.preventDefault();
form.submit()
.then(response => {
form.reset();
alert('User created.');
})
.catch(error => {
alert('An error occurred.');
});
};
フォーム送信リクエストが進行中かどうかは、フォームの processing プロパティで判定できます:
<button disabled={form.processing}>
Submit
</button>
#React と Inertia の使用
React と Inertia で Laravel アプリケーションを開発する際に手早く始めたい場合は、スターターキット の利用を検討してください。Laravel のスターターキットはバックエンドとフロントエンドの認証スキャフォールドを提供します。
React と Inertia で Precognition を使う前に、まず React での Precognition の使い方 を確認してください。Inertia と React を使う場合は、Inertia 対応の Precognition ライブラリを NPM でインストールする必要があります:
npm install laravel-precognition-react-inertia
インストール後、Precognition の useForm 関数は、上記のバリデーション機能を備えた Inertia の フォームヘルパー を返します。
フォームヘルパーの submit メソッドは簡略化されており、HTTP メソッドや URL を指定する必要がありません。代わりに、Inertia の visit オプション を唯一の引数として渡せます。また、submit メソッドは React の例のように Promise を返しません。代わりに、submit に渡す visit オプションで Inertia がサポートする イベントコールバック を指定できます:
import { useForm } from 'laravel-precognition-react-inertia';
const form = useForm('post', '/users', {
name: '',
email: '',
});
const submit = (e) => {
e.preventDefault();
form.submit({
preserveScroll: true,
onSuccess: () => form.reset(),
});
};
#Alpine と Blade の使用
Laravel Precognition を使うと、フロントエンドの Alpine アプリケーションでバリデーションルールを重複させることなく、ユーザーにライブバリデーション体験を提供できます。動作を示すために、ユーザー作成フォームを作成してみましょう。
まず、ルートで Precognition を有効にするには、HandlePrecognitiveRequests ミドルウェアをルート定義に追加します。また、ルートのバリデーションルールを格納するために フォームリクエスト を作成してください:
use App\Http\Requests\CreateUserRequest;
use Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests;
Route::post('/users', function (CreateUserRequest $request) {
// ...
})->middleware([HandlePrecognitiveRequests::class]);
次に、Alpine 用の Laravel Precognition フロントエンドヘルパーを NPM でインストールしてください:
npm install laravel-precognition-alpine
次に、resources/js/app.js ファイルで Alpine に Precognition プラグインを登録します:
import Alpine from 'alpinejs';
import Precognition from 'laravel-precognition-alpine';
window.Alpine = Alpine;
Alpine.plugin(Precognition);
Alpine.start();
Laravel Precognition パッケージをインストールして登録したら、$form の「マジック」を使ってフォームオブジェクトを作成できます。HTTP メソッド(post)、ターゲット URL(/users)、初期フォームデータを渡します。
ライブバリデーションを有効にするには、フォームのデータを該当する入力にバインドし、各入力の change イベントを監視します。change イベントハンドラーでフォームの validate メソッドを呼び出し、入力名を渡します:
<form x-data="{
form: $form('post', '/register', {
name: '',
email: '',
}),
}">
@csrf
<label for="name">Name</label>
<input
id="name"
name="name"
x-model="form.name"
@change="form.validate('name')"
/>
<template x-if="form.invalid('name')">
<div x-text="form.errors.name"></div>
</template>
<label for="email">Email</label>
<input
id="email"
name="email"
x-model="form.email"
@change="form.validate('email')"
/>
<template x-if="form.invalid('email')">
<div x-text="form.errors.email"></div>
</template>
<button :disabled="form.processing">
Create User
</button>
</form>
ユーザーがフォームに入力すると、Precognition はルートのフォームリクエストのバリデーションルールに基づくライブバリデーション結果を提供します。入力が変更されると、デバウンスされた「予知」バリデーションリクエストが Laravel アプリケーションに送信されます。デバウンスタイムアウトはフォームの setValidationTimeout 関数で設定できます:
form.setValidationTimeout(3000);
バリデーションリクエストが進行中の場合、フォームの validating プロパティは true になります:
<template x-if="form.validating">
<div>Validating...</div>
</template>
バリデーションリクエストやフォーム送信時に返されたエラーは、自動的にフォームの errors オブジェクトに反映されます:
<template x-if="form.invalid('email')">
<div x-text="form.errors.email"></div>
</template>
フォームにエラーがあるかどうかは、フォームの hasErrors プロパティで判定できます:
<template x-if="form.hasErrors">
<div><!-- ... --></div>
</template>
また、入力がバリデーションに合格したか失敗したかは、フォームの valid と invalid 関数に入力名を渡して判定できます:
<template x-if="form.valid('email')">
<span>✅</span>
</template>
<template x-if="form.invalid('email')">
<span>❌</span>
</template>
フォーム入力は、変更されてバリデーションレスポンスを受け取った後でのみ、有効または無効として表示されます。
フォーム送信リクエストが進行中かどうかは、フォームの processing プロパティで判定できます:
<button :disabled="form.processing">
Submit
</button>
#古いフォームデータの再設定
上記のユーザー作成例では、Precognition を使ってライブバリデーションを行っていますが、フォームの送信は従来のサーバーサイドで行っています。そのため、サーバーサイドのフォーム送信から返された「古い」入力値やバリデーションエラーでフォームを再設定する必要があります:
<form x-data="{
form: $form('post', '/register', {
name: '{{ old('name') }}',
email: '{{ old('email') }}',
}).setErrors({{ Js::from($errors->messages()) }}),
}">
あるいは、XHR 経由でフォームを送信したい場合は、フォームの submit 関数を使えます。これは Axios のリクエストプロミスを返します:
<form
x-data="{
form: $form('post', '/register', {
name: '',
email: '',
}),
submit() {
this.form.submit()
.then(response => {
form.reset();
alert('User created.')
})
.catch(error => {
alert('An error occurred.');
});
},
}"
@submit.prevent="submit"
>
#Axios の設定
Precognition バリデーションライブラリは、Axios HTTP クライアントを使ってアプリケーションのバックエンドにリクエストを送信します。利便性のため、Axios インスタンスは必要に応じてカスタマイズできます。例えば、laravel-precognition-vue ライブラリを使う場合、アプリケーションの resources/js/app.js ファイルで各リクエストに追加のヘッダーを付与できます:
import { client } from 'laravel-precognition-vue';
client.axios().defaults.headers.common['Authorization'] = authToken;
または、すでにアプリケーション用に設定済みの Axios インスタンスがある場合は、Precognition にそのインスタンスを使うよう指示できます:
import Axios from 'axios';
import { client } from 'laravel-precognition-vue';
window.axios = Axios.create()
window.axios.defaults.headers.common['Authorization'] = authToken;
client.use(window.axios)
Inertia 版の Precognition ライブラリは、バリデーションリクエストにのみ設定済みの Axios インスタンスを使います。フォーム送信は常に Inertia 経由で送信されます。
#バリデーションルールのカスタマイズ
予知リクエスト中に実行されるバリデーションルールは、リクエストの isPrecognitive メソッドを使ってカスタマイズできます。
例えば、ユーザー作成フォームでは、パスワードの「漏洩チェック」は最終的なフォーム送信時のみ行いたい場合があります。予知バリデーションリクエストでは、パスワードが必須で8文字以上であることだけを検証します。isPrecognitive メソッドを使ってフォームリクエストのルールをカスタマイズできます:
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rules\Password;
class StoreUserRequest extends FormRequest
{
/**
* リクエストに適用されるバリデーションルールを取得します。
*
* @return array
*/
protected function rules()
{
return [
'password' => [
'required',
$this->isPrecognitive()
? Password::min(8)
: Password::min(8)->uncompromised(),
],
// ...
];
}
}
#ファイルアップロードの処理
デフォルトでは、Laravel Precognition は予知バリデーションリクエスト中にファイルをアップロードまたはバリデートしません。これにより、大きなファイルが不必要に複数回アップロードされることを防ぎます。
この挙動のため、アプリケーションでは 対応するフォームリクエストのバリデーションルールをカスタマイズ し、フィールドが完全なフォーム送信時のみ必須になるようにしてください:
/**
* リクエストに適用されるバリデーションルールを取得します。
*
* @return array
*/
protected function rules()
{
return [
'avatar' => [
...$this->isPrecognitive() ? [] : ['required'],
'image',
'mimes:jpg,png'
'dimensions:ratio=3/2',
],
// ...
];
}
すべてのバリデーションリクエストにファイルを含めたい場合は、クライアント側のフォームインスタンスで validateFiles 関数を呼び出してください:
form.validateFiles();
#副作用の管理
HandlePrecognitiveRequests ミドルウェアをルートに追加する際、予知リクエスト中にスキップすべき 他の ミドルウェアの副作用がないか検討してください。
例えば、ユーザーの「インタラクション」数を増やすミドルウェアがある場合、予知リクエストはインタラクションとしてカウントしたくないかもしれません。その場合、インタラクション数を増やす前にリクエストの isPrecognitive メソッドをチェックします:
<?php
namespace App\Http\Middleware;
use App\Facades\Interaction;
use Closure;
use Illuminate\Http\Request;
class InteractionMiddleware
{
/**
* 受信したリクエストを処理します。
*/
public function handle(Request $request, Closure $next): mixed
{
if (! $request->isPrecognitive()) {
Interaction::incrementFor($request->user());
}
return $next($request);
}
}
#テスト
テストで予知リクエストを行いたい場合、Laravel の TestCase には withPrecognition ヘルパーがあり、Precognition リクエストヘッダーを追加します。
さらに、予知リクエストが成功した(バリデーションエラーが返されなかった)ことをアサートしたい場合は、レスポンスの assertSuccessfulPrecognition メソッドを使えます:
public function test_it_validates_registration_form_with_precognition()
{
$response = $this->withPrecognition()
->post('/register', [
'name' => 'Taylor Otwell',
]);
$response->assertSuccessfulPrecognition();
$this->assertSame(0, User::count());
}