跳到内容

Precognition

简介

Laravel Precognition 允许您预测未来 HTTP 请求的结果。Precognition 的主要用例之一是为您的前端 JavaScript 应用程序提供“实时”验证,而无需复制应用程序的后端验证规则。Precognition 与 Laravel 基于 Inertia 的 入门套件 特别搭配。

当 Laravel 收到“预知请求”时,它将执行所有路由的中间件并解析路由的控制器依赖项,包括验证 表单请求 - 但它实际上不会执行路由的控制器方法。

实时验证

使用 Vue

使用 Laravel Precognition,您可以为用户提供实时验证体验,而无需在前端 Vue 应用程序中复制验证规则。为了说明其工作原理,让我们构建一个用于在我们的应用程序中创建新用户的表单。

首先,要为路由启用 Precognition,应将 HandlePrecognitiveRequests 中间件添加到路由定义中。您还应该创建一个 表单请求 来存放路由的验证规则

1use App\Http\Requests\StoreUserRequest;
2use Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests;
3 
4Route::post('/users', function (StoreUserRequest $request) {
5 // ...
6})->middleware([HandlePrecognitiveRequests::class]);

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

1npm install laravel-precognition-vue

安装 Laravel Precognition 包后,您现在可以使用 Precognition 的 useForm 函数创建一个表单对象,提供 HTTP 方法 (post)、目标 URL (/users) 和初始表单数据。

然后,要启用实时验证,请在每个输入的 change 事件上调用表单的 validate 方法,提供输入的名称

1<script setup>
2import { useForm } from 'laravel-precognition-vue';
3 
4const form = useForm('post', '/users', {
5 name: '',
6 email: '',
7});
8 
9const submit = () => form.submit();
10</script>
11 
12<template>
13 <form @submit.prevent="submit">
14 <label for="name">Name</label>
15 <input
16 id="name"
17 v-model="form.name"
18 @change="form.validate('name')"
19 />
20 <div v-if="form.invalid('name')">
21 {{ form.errors.name }}
22 </div>
23 
24 <label for="email">Email</label>
25 <input
26 id="email"
27 type="email"
28 v-model="form.email"
29 @change="form.validate('email')"
30 />
31 <div v-if="form.invalid('email')">
32 {{ form.errors.email }}
33 </div>
34 
35 <button :disabled="form.processing">
36 Create User
37 </button>
38 </form>
39</template>

现在,当用户填写表单时,Precognition 将提供由路由表单请求中的验证规则驱动的实时验证输出。当表单的输入更改时,将向您的 Laravel 应用程序发送一个去抖动的“预知”验证请求。您可以通过调用表单的 setValidationTimeout 函数来配置去抖动超时

1form.setValidationTimeout(3000);

当验证请求正在进行中时,表单的 validating 属性将为 true

1<div v-if="form.validating">
2 Validating...
3</div>

在验证请求或表单提交期间返回的任何验证错误都将自动填充表单的 errors 对象

1<div v-if="form.invalid('email')">
2 {{ form.errors.email }}
3</div>

您可以使用表单的 hasErrors 属性来确定表单是否有任何错误

1<div v-if="form.hasErrors">
2 <!-- ... -->
3</div>

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

1<span v-if="form.valid('email')">
2
3</span>
4 
5<span v-else-if="form.invalid('email')">
6
7</span>

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

如果您正在使用 Precognition 验证表单输入的子集,则手动清除错误可能很有用。您可以使用表单的 forgetError 函数来实现此目的

1<input
2 id="avatar"
3 type="file"
4 @change="(e) => {
5 form.avatar = e.target.files[0]
6 
7 form.forgetError('avatar')
8 }"
9>

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

要使用 Precognition 执行此操作,您应该调用 validate 方法,并将您希望验证的字段名称传递给 only 配置键。您可以使用 onSuccessonValidationError 回调处理验证结果

1<button
2 type="button"
3 @click="form.validate({
4 only: ['name', 'email', 'phone'],
5 onSuccess: (response) => nextStep(),
6 onValidationError: (response) => /* ... */,
7 })"
8>Next Step</button>

当然,您也可以执行代码来响应表单提交的响应。表单的 submit 函数返回一个 Axios 请求 promise。这提供了一种访问响应负载、在成功提交时重置表单输入或处理失败请求的便捷方法

1const submit = () => form.submit()
2 .then(response => {
3 form.reset();
4 
5 alert('User created.');
6 })
7 .catch(error => {
8 alert('An error occurred.');
9 });

您可以通过检查表单的 processing 属性来确定表单提交请求是否正在进行中

1<button :disabled="form.processing">
2 Submit
3</button>

使用 Vue 和 Inertia

如果您想在开发带有 Vue 和 Inertia 的 Laravel 应用程序时获得先发优势,请考虑使用我们的 入门套件 之一。Laravel 的入门套件为您的新 Laravel 应用程序提供后端和前端身份验证脚手架。

在将 Precognition 与 Vue 和 Inertia 一起使用之前,请务必查看我们关于 将 Precognition 与 Vue 一起使用 的一般文档。将 Vue 与 Inertia 一起使用时,您需要通过 NPM 安装 Inertia 兼容的 Precognition 库

1npm install laravel-precognition-vue-inertia

安装后,Precognition 的 useForm 函数将返回一个 Inertia 表单助手,其中增强了上面讨论的验证功能。

表单助手的 submit 方法已简化,无需指定 HTTP 方法或 URL。相反,您可以将 Inertia 的 访问选项 作为第一个也是唯一一个参数传递。此外,如上面的 Vue 示例所示,submit 方法不返回 Promise。相反,您可以在提供给 submit 方法的访问选项中提供 Inertia 支持的任何 事件回调

1<script setup>
2import { useForm } from 'laravel-precognition-vue-inertia';
3 
4const form = useForm('post', '/users', {
5 name: '',
6 email: '',
7});
8 
9const submit = () => form.submit({
10 preserveScroll: true,
11 onSuccess: () => form.reset(),
12});
13</script>

使用 React

使用 Laravel Precognition,您可以为用户提供实时验证体验,而无需在前端 React 应用程序中复制验证规则。为了说明其工作原理,让我们构建一个用于在我们的应用程序中创建新用户的表单。

首先,要为路由启用 Precognition,应将 HandlePrecognitiveRequests 中间件添加到路由定义中。您还应该创建一个 表单请求 来存放路由的验证规则

1use App\Http\Requests\StoreUserRequest;
2use Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests;
3 
4Route::post('/users', function (StoreUserRequest $request) {
5 // ...
6})->middleware([HandlePrecognitiveRequests::class]);

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

1npm install laravel-precognition-react

安装 Laravel Precognition 包后,您现在可以使用 Precognition 的 useForm 函数创建一个表单对象,提供 HTTP 方法 (post)、目标 URL (/users) 和初始表单数据。

要启用实时验证,您应该监听每个输入的 changeblur 事件。在 change 事件处理程序中,您应该使用 setData 函数设置表单的数据,传递输入的名称和新值。然后,在 blur 事件处理程序中调用表单的 validate 方法,提供输入的名称

1import { useForm } from 'laravel-precognition-react';
2 
3export default function Form() {
4 const form = useForm('post', '/users', {
5 name: '',
6 email: '',
7 });
8 
9 const submit = (e) => {
10 e.preventDefault();
11 
12 form.submit();
13 };
14 
15 return (
16 <form onSubmit={submit}>
17 <label htmlFor="name">Name</label>
18 <input
19 id="name"
20 value={form.data.name}
21 onChange={(e) => form.setData('name', e.target.value)}
22 onBlur={() => form.validate('name')}
23 />
24 {form.invalid('name') && <div>{form.errors.name}</div>}
25 
26 <label htmlFor="email">Email</label>
27 <input
28 id="email"
29 value={form.data.email}
30 onChange={(e) => form.setData('email', e.target.value)}
31 onBlur={() => form.validate('email')}
32 />
33 {form.invalid('email') && <div>{form.errors.email}</div>}
34 
35 <button disabled={form.processing}>
36 Create User
37 </button>
38 </form>
39 );
40};

现在,当用户填写表单时,Precognition 将提供由路由表单请求中的验证规则驱动的实时验证输出。当表单的输入更改时,将向您的 Laravel 应用程序发送一个去抖动的“预知”验证请求。您可以通过调用表单的 setValidationTimeout 函数来配置去抖动超时

1form.setValidationTimeout(3000);

当验证请求正在进行中时,表单的 validating 属性将为 true

1{form.validating && <div>Validating...</div>}

在验证请求或表单提交期间返回的任何验证错误都将自动填充表单的 errors 对象

1{form.invalid('email') && <div>{form.errors.email}</div>}

您可以使用表单的 hasErrors 属性来确定表单是否有任何错误

1{form.hasErrors && <div><!-- ... --></div>}

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

1{form.valid('email') && <span></span>}
2 
3{form.invalid('email') && <span></span>}

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

如果您正在使用 Precognition 验证表单输入的子集,则手动清除错误可能很有用。您可以使用表单的 forgetError 函数来实现此目的

1<input
2 id="avatar"
3 type="file"
4 onChange={(e) => {
5 form.setData('avatar', e.target.value);
6 
7 form.forgetError('avatar');
8 }}
9>

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

要使用 Precognition 执行此操作,您应该调用 validate 方法,并将您希望验证的字段名称传递给 only 配置键。您可以使用 onSuccessonValidationError 回调处理验证结果

1<button
2 type="button"
3 onClick={() => form.validate({
4 only: ['name', 'email', 'phone'],
5 onSuccess: (response) => nextStep(),
6 onValidationError: (response) => /* ... */,
7 })}
8>Next Step</button>

当然,您也可以执行代码来响应表单提交的响应。表单的 submit 函数返回一个 Axios 请求 promise。这提供了一种访问响应负载、在成功提交表单时重置表单的输入或处理失败请求的便捷方法

1const submit = (e) => {
2 e.preventDefault();
3 
4 form.submit()
5 .then(response => {
6 form.reset();
7 
8 alert('User created.');
9 })
10 .catch(error => {
11 alert('An error occurred.');
12 });
13};

您可以通过检查表单的 processing 属性来确定表单提交请求是否正在进行中

1<button disabled={form.processing}>
2 Submit
3</button>

使用 React 和 Inertia

如果您想在开发带有 React 和 Inertia 的 Laravel 应用程序时获得先发优势,请考虑使用我们的 入门套件 之一。Laravel 的入门套件为您的新 Laravel 应用程序提供后端和前端身份验证脚手架。

在将 Precognition 与 React 和 Inertia 一起使用之前,请务必查看我们关于 将 Precognition 与 React 一起使用 的一般文档。将 React 与 Inertia 一起使用时,您需要通过 NPM 安装 Inertia 兼容的 Precognition 库

1npm install laravel-precognition-react-inertia

安装后,Precognition 的 useForm 函数将返回一个 Inertia 表单助手,其中增强了上面讨论的验证功能。

表单助手的 submit 方法已简化,无需指定 HTTP 方法或 URL。相反,您可以将 Inertia 的 访问选项 作为第一个也是唯一一个参数传递。此外,如上面的 React 示例所示,submit 方法不返回 Promise。相反,您可以在提供给 submit 方法的访问选项中提供 Inertia 支持的任何 事件回调

1import { useForm } from 'laravel-precognition-react-inertia';
2 
3const form = useForm('post', '/users', {
4 name: '',
5 email: '',
6});
7 
8const submit = (e) => {
9 e.preventDefault();
10 
11 form.submit({
12 preserveScroll: true,
13 onSuccess: () => form.reset(),
14 });
15};

使用 Alpine 和 Blade

使用 Laravel Precognition,您可以为用户提供实时验证体验,而无需在前端 Alpine 应用程序中复制验证规则。为了说明其工作原理,让我们构建一个用于在我们的应用程序中创建新用户的表单。

首先,要为路由启用 Precognition,应将 HandlePrecognitiveRequests 中间件添加到路由定义中。您还应该创建一个 表单请求 来存放路由的验证规则

1use App\Http\Requests\CreateUserRequest;
2use Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests;
3 
4Route::post('/users', function (CreateUserRequest $request) {
5 // ...
6})->middleware([HandlePrecognitiveRequests::class]);

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

1npm install laravel-precognition-alpine

然后,在您的 resources/js/app.js 文件中向 Alpine 注册 Precognition 插件

1import Alpine from 'alpinejs';
2import Precognition from 'laravel-precognition-alpine';
3 
4window.Alpine = Alpine;
5 
6Alpine.plugin(Precognition);
7Alpine.start();

安装并注册 Laravel Precognition 包后,您现在可以使用 Precognition 的 $form“魔法”创建一个表单对象,提供 HTTP 方法 (post)、目标 URL (/users) 和初始表单数据。

要启用实时验证,您应该将表单的数据绑定到其相关输入,然后监听每个输入的 change 事件。在 change 事件处理程序中,您应该调用表单的 validate 方法,提供输入的名称

1<form x-data="{
2 form: $form('post', '/register', {
3 name: '',
4 email: '',
5 }),
6}">
7 @csrf
8 <label for="name">Name</label>
9 <input
10 id="name"
11 name="name"
12 x-model="form.name"
13 @change="form.validate('name')"
14 />
15 <template x-if="form.invalid('name')">
16 <div x-text="form.errors.name"></div>
17 </template>
18 
19 <label for="email">Email</label>
20 <input
21 id="email"
22 name="email"
23 x-model="form.email"
24 @change="form.validate('email')"
25 />
26 <template x-if="form.invalid('email')">
27 <div x-text="form.errors.email"></div>
28 </template>
29 
30 <button :disabled="form.processing">
31 Create User
32 </button>
33</form>

现在,当用户填写表单时,Precognition 将提供由路由表单请求中的验证规则驱动的实时验证输出。当表单的输入更改时,将向您的 Laravel 应用程序发送一个去抖动的“预知”验证请求。您可以通过调用表单的 setValidationTimeout 函数来配置去抖动超时

1form.setValidationTimeout(3000);

当验证请求正在进行中时,表单的 validating 属性将为 true

1<template x-if="form.validating">
2 <div>Validating...</div>
3</template>

在验证请求或表单提交期间返回的任何验证错误都将自动填充表单的 errors 对象

1<template x-if="form.invalid('email')">
2 <div x-text="form.errors.email"></div>
3</template>

您可以使用表单的 hasErrors 属性来确定表单是否有任何错误

1<template x-if="form.hasErrors">
2 <div><!-- ... --></div>
3</template>

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

1<template x-if="form.valid('email')">
2 <span></span>
3</template>
4 
5<template x-if="form.invalid('email')">
6 <span></span>
7</template>

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

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

要使用 Precognition 执行此操作,您应该调用 validate 方法,并将您希望验证的字段名称传递给 only 配置键。您可以使用 onSuccessonValidationError 回调处理验证结果

1<button
2 type="button"
3 @click="form.validate({
4 only: ['name', 'email', 'phone'],
5 onSuccess: (response) => nextStep(),
6 onValidationError: (response) => /* ... */,
7 })"
8>Next Step</button>

您可以通过检查表单的 processing 属性来确定表单提交请求是否正在进行中

1<button :disabled="form.processing">
2 Submit
3</button>

重新填充旧表单数据

在上面讨论的用户创建示例中,我们使用 Precognition 执行实时验证;但是,我们正在执行传统的服务器端表单提交来提交表单。因此,表单应该填充从服务器端表单提交返回的任何“旧”输入和验证错误

1<form x-data="{
2 form: $form('post', '/register', {
3 name: '{{ old('name') }}',
4 email: '{{ old('email') }}',
5 }).setErrors({{ Js::from($errors->messages()) }}),
6}">

或者,如果您想通过 XHR 提交表单,您可以使用表单的 submit 函数,该函数返回一个 Axios 请求 promise

1<form
2 x-data="{
3 form: $form('post', '/register', {
4 name: '',
5 email: '',
6 }),
7 submit() {
8 this.form.submit()
9 .then(response => {
10 form.reset();
11 
12 alert('User created.')
13 })
14 .catch(error => {
15 alert('An error occurred.');
16 });
17 },
18 }"
19 @submit.prevent="submit"
20>

配置 Axios

Precognition 验证库使用 Axios HTTP 客户端向您的应用程序后端发送请求。为方便起见,如果您的应用程序需要,可以自定义 Axios 实例。例如,当使用 laravel-precognition-vue 库时,您可以在应用程序的 resources/js/app.js 文件中向每个传出请求添加额外的请求标头

1import { client } from 'laravel-precognition-vue';
2 
3client.axios().defaults.headers.common['Authorization'] = authToken;

或者,如果您已经为您的应用程序配置了 Axios 实例,您可以告诉 Precognition 使用该实例代替

1import Axios from 'axios';
2import { client } from 'laravel-precognition-vue';
3 
4window.axios = Axios.create()
5window.axios.defaults.headers.common['Authorization'] = authToken;
6 
7client.use(window.axios)

Inertia 风格的 Precognition 库将仅使用配置的 Axios 实例进行验证请求。表单提交将始终由 Inertia 发送。

自定义验证规则

可以通过使用请求的 isPrecognitive 方法自定义在预知请求期间执行的验证规则。

例如,在用户创建表单上,我们可能希望仅在最终表单提交时验证密码是否“未泄露”。对于预知验证请求,我们将仅验证密码是否为必填项且至少包含 8 个字符。使用 isPrecognitive 方法,我们可以自定义由我们的表单请求定义的规则

1<?php
2 
3namespace App\Http\Requests;
4 
5use Illuminate\Foundation\Http\FormRequest;
6use Illuminate\Validation\Rules\Password;
7 
8class StoreUserRequest extends FormRequest
9{
10 /**
11 * Get the validation rules that apply to the request.
12 *
13 * @return array
14 */
15 protected function rules()
16 {
17 return [
18 'password' => [
19 'required',
20 $this->isPrecognitive()
21 ? Password::min(8)
22 : Password::min(8)->uncompromised(),
23 ],
24 // ...
25 ];
26 }
27}

处理文件上传

默认情况下,Laravel Precognition 不会在预知验证请求期间上传或验证文件。这确保不会不必要地多次上传大文件。

由于此行为,您应确保您的应用程序 自定义相应的表单请求的验证规则 以指定该字段仅在完整表单提交时才是必需的

1/**
2 * Get the validation rules that apply to the request.
3 *
4 * @return array
5 */
6protected function rules()
7{
8 return [
9 'avatar' => [
10 ...$this->isPrecognitive() ? [] : ['required'],
11 'image',
12 'mimes:jpg,png',
13 'dimensions:ratio=3/2',
14 ],
15 // ...
16 ];
17}

如果您想在每个验证请求中包含文件,您可以在客户端表单实例上调用 validateFiles 函数

1form.validateFiles();

管理副作用

HandlePrecognitiveRequests 中间件添加到路由时,您应该考虑在预知请求期间是否应跳过其他中间件中的任何副作用。

例如,您可能有一个中间件,用于递增每个用户与您的应用程序的总“交互”次数,但您可能不希望将预知请求计为交互。为了实现这一点,我们可以在递增交互计数之前检查请求的 isPrecognitive 方法

1<?php
2 
3namespace App\Http\Middleware;
4 
5use App\Facades\Interaction;
6use Closure;
7use Illuminate\Http\Request;
8 
9class InteractionMiddleware
10{
11 /**
12 * Handle an incoming request.
13 */
14 public function handle(Request $request, Closure $next): mixed
15 {
16 if (! $request->isPrecognitive()) {
17 Interaction::incrementFor($request->user());
18 }
19 
20 return $next($request);
21 }
22}

测试

如果您想在测试中发出预知请求,Laravel 的 TestCase 包含一个 withPrecognition 助手,它将添加 Precognition 请求标头。

此外,如果您想断言预知请求已成功,例如,未返回任何验证错误,您可以使用响应中的 assertSuccessfulPrecognition 方法

1it('validates registration form with precognition', function () {
2 $response = $this->withPrecognition()
3 ->post('/register', [
4 'name' => 'Taylor Otwell',
5 ]);
6 
7 $response->assertSuccessfulPrecognition();
8 
9 expect(User::count())->toBe(0);
10});
1public function test_it_validates_registration_form_with_precognition()
2{
3 $response = $this->withPrecognition()
4 ->post('/register', [
5 'name' => 'Taylor Otwell',
6 ]);
7 
8 $response->assertSuccessfulPrecognition();
9 $this->assertSame(0, User::count());
10}