Laravel Precognition 允许您预知未来 HTTP 请求的结果。Precognition 的主要用例之一是能够为您的前端 JavaScript 应用程序提供 "实时" 验证,而无需重复您的应用程序的后端验证规则。
当 Laravel 收到一个"预认知请求", 它将执行路由的所有中间件并解析路由的控制器依赖项, 包括验证表单请求 - 但它实际上不会执行路由的控制器方法。
使用 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]);接下来,你应该通过 NPM 安装适用于 Vue 的 Laravel Precognition 前端辅助工具:
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>[!WARNING]
表单输入只有在发生变化并收到验证响应后,才会显示为有效或无效。
如果您正在使用 Precognition 验证表单的部分输入,手动清除错误可能会很有用。您可以使用表单的 forgetError 函数来实现此目的:
<input
id="avatar"
type="file"
@change="(e) => {
form.avatar = e.target.files[0]
form.forgetError('avatar')
}"
>正如我们所看到的,你可以挂接到输入的 change 事件并在用户与它们交互时验证单个输入; however, you may need to validate inputs that the user has not yet interacted with. This is common when building a "wizard", where you want to validate all visible inputs, whether the user has interacted with them or not, before moving to the next step.
要使用 Precognition 执行此操作, 您应该调用 validate 方法 传递您希望验证的字段名称到 only 配置键. 您可以使用 onSuccess 或 onValidationError 回调处理验证结果:
<button
type="button"
@click="form.validate({
only: ['name', 'email', 'phone'],
onSuccess: (response) => nextStep(),
onValidationError: (response) => /* ... */,
})"
>Next Step</button>当然,你也可以执行代码以响应表单提交的响应。表单的 submit 函数返回一个 Axios 请求 promise。这提供了一种便捷的方式来访问响应负载,在成功提交时重置表单输入,或处理失败的请求:
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 应用程序时获得先发优势,可以考虑使用我们提供的入门套件。Laravel 的入门套件为您的新 Laravel 应用程序提供后端和前端认证脚手架。
在将 Precognition 与 Vue 和 Inertia 配合使用之前,请务必查阅我们关于 Precognition 与 Vue 的使用 的通用文档。当将 Vue 与 Inertia 配合使用时,您需要通过 NPM 安装与 Inertia 兼容的 Precognition 库:
npm install laravel-precognition-vue-inertia安装后,Precognition 的 useForm 函数将返回一个 Inertia 表单助手,并增强了上述讨论的验证功能。
表单助手的submit方法已得到简化,无需再指定 HTTP 方法或 URL. 取而代之,你可以传入 Inertia 的访问选项作为第一个也是唯一一个参数. 此外,submit方法不会返回 Promise,如上方的 Vue 示例所示. 取而代之,你可以提供 Inertia 支持的任何事件回调,作为传入submit方法的访问选项:
<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>使用 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]);接下来,你应该通过 NPM 安装用于 React 的 Laravel Precognition 前端辅助工具:
npm install laravel-precognition-react在安装了 Laravel Precognition 包后,你现在可以使用 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 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 函数来配置防抖超时:
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.files[0]);
form.forgetError('avatar');
}}
>如我们所见,你可以钩入输入框的 blur 事件并在用户与其交互时验证单个输入框;然而,你可能需要验证那些用户尚未与其交互的输入框。这在构建“向导”时很常见,在这种情况下,你希望在进入下一步之前,验证所有可见的输入框,无论用户是否与其交互过。
使用 Precognition 完成此操作,您应该调用 validate 方法,并将您希望验证的字段名称传递给 only 配置键。您可以通过 onSuccess 或 onValidationError 回调来处理验证结果:
<button
type="button"
onClick={() => form.validate({
only: ['name', 'email', 'phone'],
onSuccess: (response) => nextStep(),
onValidationError: (response) => /* ... */,
})}
>Next Step</button>当然,你也可以根据表单提交的响应执行代码。表单的submit函数返回一个Axios请求Promise。这提供了一种便捷的方式来访问响应负载,在表单成功提交后重置表单输入,或处理失败的请求:
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>[!NOTE]
如果您希望在使用 React 和 Inertia 开发 Laravel 应用程序时能够快速上手,可以考虑使用我们提供的 入门套件。Laravel 的入门套件为您的新 Laravel 应用程序提供了后端和前端认证脚手架。
在使用 Precognition 结合 React 和 Inertia 之前,请务必查阅我们关于将 Precognition 与 React 结合使用的通用文档。当将 React 与 Inertia 结合使用时,您需要通过 NPM 安装与 Inertia 兼容的 Precognition 库:
npm install laravel-precognition-react-inertia安装后,Precognition 的 useForm 函数将返回一个 Inertia 表单助手,它增强了上述验证功能。
表单助手的 submit 方法已得到简化,无需指定 HTTP 方法或 URL。相反地,你可以将 Inertia 的 访问选项 作为第一个也是唯一一个参数传递。此外,submit 方法不会返回 Promise,如同上面 React 示例所示。相反地,你可以在提供给 submit 方法的访问选项中提供 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(),
});
};借助于 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]);接下来,你需要通过 NPM 安装用于 Alpine 的 Laravel Precognition 前端辅助工具:
npm install laravel-precognition-alpine然后,注册 Precognition 插件到 Alpine 在你的 resources/js/app.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 方法,并提供输入的名称:
<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>[!WARNING]
表单输入只有在它已更改并且已收到验证响应后,才会显示为有效或无效。
正如我们所看到的,您可以挂接到输入框的 change 事件并在用户与它们交互时验证单个输入;然而,您可能需要验证用户尚未与之交互的输入。这在构建一个“向导”时很常见,此时您希望验证所有可见输入,无论用户是否与它们交互过,在进入下一步之前。
要使用 Precognition 完成此操作,您应该调用 validate 方法,并将您希望验证的字段名称传入到 only 配置键。您可以使用 onSuccess 或 onValidationError 回调来处理验证结果:
<button
type="button"
@click="form.validate({
only: ['name', 'email', 'phone'],
onSuccess: (response) => nextStep(),
onValidationError: (response) => /* ... */,
})"
>Next Step</button>您可以通过检查表单的 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 请求 promise:
<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"
>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)[!WARNING]
Inertia 风格的 Precognition 库将只使用配置好的 Axios 实例进行验证请求。表单提交将始终由 Inertia 发送。
可以通过使用请求的 isPrecognitive 方法,自定义在预认知请求期间执行的验证规则。
For example, on a user creation form, we may want to validate that a password is "uncompromised" only on the final form submission. For precognitive validation requests, we will simply validate that the password is required and has a minimum of 8 characters. Using the isPrecognitive method, we can customize the rules defined by our form request:
<?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());
}