主题
Precognition
简介
Laravel Precognition 允许你预测未来 HTTP 请求的结果。Precognition 的主要用例之一是能够为前端 JavaScript 应用提供「实时」验证,而无需在前端重复编写后端的验证规则。
当 Laravel 接收到一个「预认知请求」时,它会执行路由的所有 middleware 并解析路由的控制器依赖,包括验证表单请求——但不会实际执行路由的控制器方法。
NOTE
从 Inertia 2.3 开始,Precognition 支持已内置。请参阅 Inertia Forms 文档获取更多信息。早期的 Inertia 版本需要 Precognition 0.x。
实时验证
使用 Vue
使用 Laravel Precognition,你可以为用户提供实时验证体验,而无需在前端 Vue 应用中重复编写验证规则。为了说明它的工作原理,让我们在应用中构建一个创建新用户的表单。
首先,要为路由启用 Precognition,应将 HandlePrecognitiveRequests middleware 添加到路由定义中。你还应该创建一个表单请求来存放路由的验证规则:
php
use App\Http\Requests\StoreUserRequest;
use Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests;
Route::post('/users', function (StoreUserRequest $request) {
// ...
})->middleware([HandlePrecognitiveRequests::class]);接下来,你应该通过 NPM 安装 Laravel Precognition 的 Vue 前端辅助工具:
shell
npm install laravel-precognition-vue安装 Laravel Precognition 包后,你现在可以使用 Precognition 的 useForm 函数创建表单对象,提供 HTTP 方法(post)、目标 URL(/users)和初始表单数据。
然后,要启用实时验证,请在每个输入的 change 事件上调用表单的 validate 方法,并提供输入的名称:
vue
<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 函数来配置防抖超时时间:
js
form.setValidationTimeout(3000);当验证请求正在进行中时,表单的 validating 属性将为 true:
html
<div v-if="form.validating">
Validating...
</div>在验证请求或表单提交期间返回的任何验证错误都会自动填充到表单的 errors 对象中:
html
<div v-if="form.invalid('email')">
{{ form.errors.email }}
</div>你可以使用表单的 hasErrors 属性来确定表单是否有任何错误:
html
<div v-if="form.hasErrors">
<!-- ... -->
</div>你还可以通过将输入的名称分别传递给表单的 valid 和 invalid 函数来确定输入是否通过或未通过验证:
html
<span v-if="form.valid('email')">
✅
</span>
<span v-else-if="form.invalid('email')">
❌
</span>WARNING
表单输入只有在发生更改并且收到验证响应后才会显示为有效或无效。
如果你使用 Precognition 验证表单输入的子集,手动清除错误可能会很有用。你可以使用表单的 forgetError 函数来实现这一点:
html
<input
id="avatar"
type="file"
@change="(e) => {
form.avatar = e.target.files[0]
form.forgetError('avatar')
}"
>如我们所见,你可以挂钩输入的 change 事件并在用户与输入交互时验证单个输入;但是,你可能需要验证用户尚未交互的输入。这在构建「向导」时很常见,你希望在进入下一步之前验证所有可见的输入,无论用户是否与它们交互过。
要使用 Precognition 实现这一点,你应该调用 validate 方法,将你希望验证的字段名称传递给 only 配置键。你可以使用 onSuccess 或 onValidationError 回调来处理验证结果:
html
<button
type="button"
@click="form.validate({
only: ['name', 'email', 'phone'],
onSuccess: (response) => nextStep(),
onValidationError: (response) => /* ... */,
})"
>Next Step</button>当然,你也可以在表单提交的响应中执行代码。表单的 submit 函数返回一个 Axios 请求 promise。这提供了一种便捷的方式来访问响应数据、在成功提交时重置表单输入或处理失败的请求:
js
const submit = () => form.submit()
.then(response => {
form.reset();
alert('User created.');
})
.catch(error => {
alert('An error occurred.');
});你可以通过检查表单的 processing 属性来确定表单提交请求是否正在进行中:
html
<button :disabled="form.processing">
Submit
</button>使用 React
使用 Laravel Precognition,你可以为用户提供实时验证体验,而无需在前端 React 应用中重复编写验证规则。为了说明它的工作原理,让我们在应用中构建一个创建新用户的表单。
首先,要为路由启用 Precognition,应将 HandlePrecognitiveRequests middleware 添加到路由定义中。你还应该创建一个表单请求来存放路由的验证规则:
php
use App\Http\Requests\StoreUserRequest;
use Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests;
Route::post('/users', function (StoreUserRequest $request) {
// ...
})->middleware([HandlePrecognitiveRequests::class]);接下来,你应该通过 NPM 安装 Laravel Precognition 的 React 前端辅助工具:
shell
npm install laravel-precognition-react安装 Laravel Precognition 包后,你现在可以使用 Precognition 的 useForm 函数创建表单对象,提供 HTTP 方法(post)、目标 URL(/users)和初始表单数据。
要启用实时验证,你应该监听每个输入的 change 和 blur 事件。在 change 事件处理程序中,你应该使用 setData 函数设置表单数据,传递输入的名称和新值。然后,在 blur 事件处理程序中调用表单的 validate 方法,提供输入的名称:
jsx
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 htmlFor="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 htmlFor="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 函数来配置防抖超时时间:
js
form.setValidationTimeout(3000);当验证请求正在进行中时,表单的 validating 属性将为 true:
jsx
{form.validating && <div>Validating...</div>}在验证请求或表单提交期间返回的任何验证错误都会自动填充到表单的 errors 对象中:
jsx
{form.invalid('email') && <div>{form.errors.email}</div>}你可以使用表单的 hasErrors 属性来确定表单是否有任何错误:
jsx
{form.hasErrors && <div><!-- ... --></div>}你还可以通过将输入的名称分别传递给表单的 valid 和 invalid 函数来确定输入是否通过或未通过验证:
jsx
{form.valid('email') && <span>✅</span>}
{form.invalid('email') && <span>❌</span>}WARNING
表单输入只有在发生更改并且收到验证响应后才会显示为有效或无效。
如果你使用 Precognition 验证表单输入的子集,手动清除错误可能会很有用。你可以使用表单的 forgetError 函数来实现这一点:
jsx
<input
id="avatar"
type="file"
onChange={(e) => {
form.setData('avatar', e.target.files[0]);
form.forgetError('avatar');
}}
>如我们所见,你可以挂钩输入的 blur 事件并在用户与输入交互时验证单个输入;但是,你可能需要验证用户尚未交互的输入。这在构建「向导」时很常见,你希望在进入下一步之前验证所有可见的输入,无论用户是否与它们交互过。
要使用 Precognition 实现这一点,你应该调用 validate 方法,将你希望验证的字段名称传递给 only 配置键。你可以使用 onSuccess 或 onValidationError 回调来处理验证结果:
jsx
<button
type="button"
onClick={() => form.validate({
only: ['name', 'email', 'phone'],
onSuccess: (response) => nextStep(),
onValidationError: (response) => /* ... */,
})}
>Next Step</button>当然,你也可以在表单提交的响应中执行代码。表单的 submit 函数返回一个 Axios 请求 promise。这提供了一种便捷的方式来访问响应数据、在成功提交时重置表单输入或处理失败的请求:
js
const submit = (e) => {
e.preventDefault();
form.submit()
.then(response => {
form.reset();
alert('User created.');
})
.catch(error => {
alert('An error occurred.');
});
};你可以通过检查表单的 processing 属性来确定表单提交请求是否正在进行中:
html
<button disabled={form.processing}>
Submit
</button>使用 Alpine 和 Blade
使用 Laravel Precognition,你可以为用户提供实时验证体验,而无需在前端 Alpine 应用中重复编写验证规则。为了说明它的工作原理,让我们在应用中构建一个创建新用户的表单。
首先,要为路由启用 Precognition,应将 HandlePrecognitiveRequests middleware 添加到路由定义中。你还应该创建一个表单请求来存放路由的验证规则:
php
use App\Http\Requests\CreateUserRequest;
use Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests;
Route::post('/users', function (CreateUserRequest $request) {
// ...
})->middleware([HandlePrecognitiveRequests::class]);接下来,你应该通过 NPM 安装 Laravel Precognition 的 Alpine 前端辅助工具:
shell
npm install laravel-precognition-alpine然后,在 resources/js/app.js 文件中将 Precognition 插件注册到 Alpine:
js
import Alpine from 'alpinejs';
import Precognition from 'laravel-precognition-alpine';
window.Alpine = Alpine;
Alpine.plugin(Precognition);
Alpine.start();安装并注册 Laravel Precognition 包后,你现在可以使用 Precognition 的 $form「魔术」方法创建表单对象,提供 HTTP 方法(post)、目标 URL(/users)和初始表单数据。
要启用实时验证,你应该将表单的数据绑定到其相关输入,然后监听每个输入的 change 事件。在 change 事件处理程序中,你应该调用表单的 validate 方法,提供输入的名称:
html
<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 函数来配置防抖超时时间:
js
form.setValidationTimeout(3000);当验证请求正在进行中时,表单的 validating 属性将为 true:
html
<template x-if="form.validating">
<div>Validating...</div>
</template>在验证请求或表单提交期间返回的任何验证错误都会自动填充到表单的 errors 对象中:
html
<template x-if="form.invalid('email')">
<div x-text="form.errors.email"></div>
</template>你可以使用表单的 hasErrors 属性来确定表单是否有任何错误:
html
<template x-if="form.hasErrors">
<div><!-- ... --></div>
</template>你还可以通过将输入的名称分别传递给表单的 valid 和 invalid 函数来确定输入是否通过或未通过验证:
html
<template x-if="form.valid('email')">
<span>✅</span>
</template>
<template x-if="form.invalid('email')">
<span>❌</span>
</template>WARNING
表单输入只有在发生更改并且收到验证响应后才会显示为有效或无效。
如我们所见,你可以挂钩输入的 change 事件并在用户与输入交互时验证单个输入;但是,你可能需要验证用户尚未交互的输入。这在构建「向导」时很常见,你希望在进入下一步之前验证所有可见的输入,无论用户是否与它们交互过。
要使用 Precognition 实现这一点,你应该调用 validate 方法,将你希望验证的字段名称传递给 only 配置键。你可以使用 onSuccess 或 onValidationError 回调来处理验证结果:
html
<button
type="button"
@click="form.validate({
only: ['name', 'email', 'phone'],
onSuccess: (response) => nextStep(),
onValidationError: (response) => /* ... */,
})"
>Next Step</button>你可以通过检查表单的 processing 属性来确定表单提交请求是否正在进行中:
html
<button :disabled="form.processing">
Submit
</button>重新填充旧表单数据
在上面讨论的用户创建示例中,我们使用 Precognition 执行实时验证;但是,我们使用传统的服务端表单提交来提交表单。因此,表单应填充从服务端表单提交返回的任何「旧」输入和验证错误:
html
<form x-data="{
form: $form('post', '/register', {
name: '{{ old('name') }}',
email: '{{ old('email') }}',
}).setErrors({{ Js::from($errors->messages()) }}),
}">或者,如果你想通过 XHR 提交表单,可以使用表单的 submit 函数,它返回一个 Axios 请求 promise:
html
<form
x-data="{
form: $form('post', '/register', {
name: '',
email: '',
}),
submit() {
this.form.submit()
.then(response => {
this.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 文件中为每个外发请求添加额外的请求头:
js
import { client } from 'laravel-precognition-vue';
client.axios().defaults.headers.common['Authorization'] = authToken;或者,如果你的应用已经有一个配置好的 Axios 实例,你可以告诉 Precognition 使用该实例:
js
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)验证数组
你可以使用通配符来验证数组或嵌套对象中的字段。每个 * 匹配单个路径段:
js
// Validate email for all users in an array...
form.validate('users.*.email');
// Validate all fields in a profile object...
form.validate('profile.*');
// Validate all fields for all users...
form.validate('users.*.*');自定义验证规则
可以通过使用请求的 isPrecognitive 方法来自定义预认知请求期间执行的验证规则。
例如,在用户创建表单上,我们可能希望仅在最终表单提交时验证密码是否「未泄露」。对于预认知验证请求,我们只需验证密码是必填的且最少 8 个字符。使用 isPrecognitive 方法,我们可以自定义表单请求定义的规则:
php
<?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 不会在预认知验证请求期间上传或验证文件。这确保了大文件不会被不必要地多次上传。
由于这种行为,你应该确保你的应用自定义相应表单请求的验证规则,以指定该字段仅在完整表单提交时才是必填的:
php
/**
* 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 函数:
js
form.validateFiles();管理副作用
当向路由添加 HandlePrecognitiveRequests middleware 时,你应该考虑_其他_ middleware 中是否有任何副作用应在预认知请求期间被跳过。
例如,你可能有一个 middleware 会递增每个用户与应用的「交互」总数,但你可能不希望预认知请求被计为一次交互。为此,我们可以在递增交互计数之前检查请求的 isPrecognitive 方法:
php
<?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 方法:
php
it('validates registration form with precognition', function () {
$response = $this->withPrecognition()
->post('/register', [
'name' => 'Taylor Otwell',
]);
$response->assertSuccessfulPrecognition();
expect(User::count())->toBe(0);
});php
public function test_it_validates_registration_form_with_precognition()
{
$response = $this->withPrecognition()
->post('/register', [
'name' => 'Taylor Otwell',
]);
$response->assertSuccessfulPrecognition();
$this->assertSame(0, User::count());
}