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 <input16 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 <input26 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 User37 </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>
您还可以通过将输入的名称分别传递给表单valid
和invalid
函数来确定输入是否通过验证或失败:
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<input2 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
处理验证结果:onSuccess
onValidationError
1<button2 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 Submit3</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 <input19 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 <input28 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 User37 </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>}
您还可以通过将输入的名称分别传递给表单valid
和invalid
函数来确定输入是否通过验证或失败:
1{form.valid('email') && <span>✅</span>}2 3{form.invalid('email') && <span>❌</span>}
表单输入只有在发生变化并收到验证响应后才会显示为有效或无效。
如果您使用 Precognition 验证表单输入的子集,手动清除错误会很有用。您可以使用表单forgetError
函数来实现此目的:
1<input2 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
处理验证结果:onSuccess
onValidationError
1<button2 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 Submit3</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 <input10 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 <input21 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 User32 </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>
您还可以通过将输入的名称分别传递给表单valid
和invalid
函数来确定输入是否通过验证或失败:
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
处理验证结果:onSuccess
onValidationError
1<button2 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 Submit3</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 array14 */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 InteractionMiddleware10{11 /**12 * Handle an incoming request.13 */14 public function handle(Request $request, Closure $next): mixed15 {16 if (! $request->isPrecognitive()) {17 Interaction::incrementFor($request->user());18 }19 20 return $next($request);21 }22}
测试
如果您想在测试中提出Precognition请求,LaravelTestCase
包含一个withPrecognition
Helpers,它将添加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}