メインコンテンツまでスキップ

予知

はじめに

Laravel Precognitionを使用すると、将来のHTTPリクエストの結果を予測することができます。Precognitionの主なユースケースの1つは、フロントエンドの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パッケージをインストールしたら、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 の使用

注記

LaravelアプリケーションをVueとInertiaで開発する際にヘッドスタートを切りたい場合は、当社のスターターキットのいずれかを使用することを検討してください。Laravelのスターターキットには、新しいLaravelアプリケーション用のバックエンドとフロントエンドの認証スキャフォールディングが提供されます。

VueとInertiaを使用する前に、VueでPrecognitionを使用するに関する一般的なドキュメントを確認してください。VueをInertiaと一緒に使用する場合は、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パッケージをインストールしたら、PrecognitionのuseForm関数を使用してフォームオブジェクトを作成し、HTTPメソッド(post)、ターゲットURL(/users)、および初期フォームデータを指定できます。

ライブバリデーションを有効にするには、各入力の changeblur イベントをリッスンする必要があります。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 のスターターキットには、新しい Laravel アプリケーションのバックエンドとフロントエンドの認証スキャフォールディングが提供されます。

React と Inertia と一緒に Precognition を使用する前に、React で Precognition を使用する に関する一般的なドキュメントを確認してください。React と Inertia を使用する際には、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パッケージがインストールおよび登録されたら、Precognitionの$form "magic"を使用して、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 flavoredの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
{
/**
* Get the validation rules that apply to the request.
*
* @return array
*/
protected function rules()
{
return [
'password' => [
'required',
$this->isPrecognitive()
? Password::min(8)
: Password::min(8)->uncompromised(),
],
// ...
];
}
}

ファイルのアップロードの処理

Laravel Precognition は、デフォルトでは予知的な検証リクエスト中にファイルをアップロードしたり検証したりしません。これにより、大きなファイルが不必要に複数回アップロードされることがありません。

この動作のため、アプリケーションが対応するフォームリクエストの検証ルールをカスタマイズして、フィールドが完全なフォーム送信のためにのみ必要であることを確認する必要があります:

/**
* Get the validation rules that apply to the request.
*
* @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
{
/**
* Handle an incoming request.
*/
public function handle(Request $request, Closure $next): mixed
{
if (! $request->isPrecognitive()) {
Interaction::incrementFor($request->user());
}

return $next($request);
}
}

テスト

テストで予知的なリクエストを行いたい場合、Laravel の TestCase には withPrecognition ヘルパーが含まれており、Precognition リクエストヘッダーが追加されます。

さらに、予知的なリクエストが成功したことをアサートしたい場合、つまり、検証エラーが返されなかったことを確認したい場合は、レスポンスに対して assertSuccessfulPrecognition メソッドを使用できます:

it('validates registration form with precognition', function () {
$response = $this->withPrecognition()
->post('/register', [
'name' => 'Taylor Otwell',
]);

$response->assertSuccessfulPrecognition();

expect(User::count())->toBe(0);
});
public function test_it_validates_registration_form_with_precognition()
{
$response = $this->withPrecognition()
->post('/register', [
'name' => 'Taylor Otwell',
]);

$response->assertSuccessfulPrecognition();
$this->assertSame(0, User::count());
}