跳至内容

Precognition

简介

Laravel Precognition 允许你预测未来 HTTP 请求的结果。Precognition 的主要用例之一是能够为你的前端 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]);

接下来,你应该通过 NPM 安装 Laravel Precognition 前端助手以用于 Vue

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>

你还可以通过将输入的名称分别传递给表单的 validinvalid 函数来确定输入是否通过或未通过验证

<span v-if="form.valid('email')">
</span>
 
<span v-else-if="form.invalid('email')">
</span>
exclamation

表单输入只有在发生更改并收到验证响应后才会显示为有效或无效。

如果你使用 Precognition 验证表单输入的子集,则手动清除错误可能很有用。你可以使用表单的 forgetError 函数来实现这一点

<input
id="avatar"
type="file"
@change="(e) => {
form.avatar = e.target.files[0]
 
form.forgetError('avatar')
}"
>

正如我们所见,你可以挂钩到输入的 change 事件并验证用户与之交互的各个输入;但是,你可能需要验证用户尚未与之交互的输入。这在构建“向导”时很常见,在“向导”中,你希望验证所有可见的输入,无论用户是否与之交互,然后再转到下一步。

要使用 Precognition 完成此操作,你应该通过将它们的名字传递给 touch 方法将你希望验证的字段标记为“已触碰”。然后,使用 onSuccessonValidationError 回调调用 validate 方法

<button
type="button"
@click="form.touch(['name', 'email', 'phone']).validate({
onSuccess: (response) => nextStep(),
onValidationError: (response) => /* ... */,
})"
>Next Step</button>

当然,你也可以在响应表单提交时执行代码。表单的 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

lightbulb

如果你希望在使用 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 方法不会像上面的 Vue 示例那样返回一个 Promise。相反,你可以在传递给 submit 方法的访问选项中提供 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]);

接下来,你应该通过 NPM 安装 Laravel Precognition 前端助手以用于 React

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 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>}

你还可以通过将输入的名称分别传递给表单的 validinvalid 函数来确定输入是否通过或未通过验证

{form.valid('email') && <span></span>}
 
{form.invalid('email') && <span></span>}
exclamation

表单输入只有在发生更改并收到验证响应后才会显示为有效或无效。

如果你使用 Precognition 验证表单输入的子集,则手动清除错误可能很有用。你可以使用表单的 forgetError 函数来实现这一点

<input
id="avatar"
type="file"
onChange={(e) => {
form.setData('avatar', e.target.value);
 
form.forgetError('avatar');
}}
>

正如我们所见,你可以挂钩到输入的 blur 事件并验证用户与之交互的各个输入;但是,你可能需要验证用户尚未与之交互的输入。这在构建“向导”时很常见,在“向导”中,你希望验证所有可见的输入,无论用户是否与之交互,然后再转到下一步。

要使用 Precognition 完成此操作,你应该通过将它们的名字传递给 touch 方法将你希望验证的字段标记为“已触碰”。然后,使用 onSuccessonValidationError 回调调用 validate 方法

<button
type="button"
onClick={() => form.touch(['name', 'email', 'phone']).validate({
onSuccess: (response) => nextStep(),
onValidationError: (response) => /* ... */,
})}
>Next Step</button>

当然,你也可以在响应表单提交时执行代码。表单的 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

lightbulb

如果你希望在使用 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(),
});
};

使用 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]);

接下来,您应该通过 NPM 为 Alpine 安装 Laravel Precognition 前端助手

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 "魔法" 创建表单对象,提供 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>

你还可以通过将输入的名称分别传递给表单的 validinvalid 函数来确定输入是否通过或未通过验证

<template x-if="form.valid('email')">
<span></span>
</template>
 
<template x-if="form.invalid('email')">
<span></span>
</template>
exclamation

表单输入只有在发生更改并收到验证响应后才会显示为有效或无效。

正如我们所见,你可以挂钩到输入的 change 事件并验证用户与之交互的各个输入;但是,你可能需要验证用户尚未与之交互的输入。这在构建“向导”时很常见,在“向导”中,你希望验证所有可见的输入,无论用户是否与之交互,然后再转到下一步。

要使用 Precognition 完成此操作,你应该通过将它们的名字传递给 touch 方法将你希望验证的字段标记为“已触碰”。然后,使用 onSuccessonValidationError 回调调用 validate 方法

<button
type="button"
@change="form.touch(['name', 'email', 'phone']).validate({
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 请求承诺

<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)
exclamation

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
{
/**
* 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());
}