跳至内容

Precognition

介绍

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

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

实时验证

使用 Vue

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

首先,要为路由启用预识别功能,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 为 Vue 安装 Laravel Precognition 前端Helpers:

1npm install laravel-precognition-vue

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

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

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 请求承诺。这提供了一种便捷的方式来访问响应有效负载、在提交成功时重置表单输入或处理失败的请求:

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表单Helpers,并增强上面讨论的验证功能。

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

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 应用程序中重复验证规则。为了说明其工作原理,让我们在应用程序中构建一个用于创建新用户的表单。

首先,要为路由启用预识别功能,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 为 React 安装 Laravel Precognition 前端Helpers:

1npm install laravel-precognition-react

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

要启用实时验证,您应该监听每个输入框的change事件blur。在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 请求承诺。这提供了一种便捷的方式来访问响应有效负载、在表单提交成功时重置表单的输入框,或者处理失败的请求:

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表单Helpers,并增强上面讨论的验证功能。

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

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 应用程序中重复验证规则。为了说明其工作原理,让我们在应用程序中构建一个用于创建新用户的表单。

首先,要为路由启用预识别功能,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 为 Alpine 安装 Laravel Precognition 前端Helpers:

1npm install laravel-precognition-alpine

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

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 请求承诺:

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 this.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方法自定义Precognition请求期间执行的验证规则。

例如,在用户创建表单中,我们可能只想在最终提交时验证密码是否“未被泄露”。对于Precognition验证请求,我们只需验证密码是否必填且至少包含 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();

管理副作用

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

例如,你可能有一个中间件,可以递增每个用户与应用的“交互”总数,但你可能不希望Precognition请求被计入交互。为此,我们可以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}

测试

如果您想在测试中提出Precognition请求,LaravelTestCase包含一个withPrecognitionHelpers,它将添加Precognition请求标头。

此外,如果您想断言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}