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 <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 请求 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 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 表单助手,其中增强了上面讨论的验证功能。
表单助手的 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
) 和初始表单数据。
要启用实时验证,您应该监听每个输入的 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 请求 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 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 表单助手,其中增强了上面讨论的验证功能。
表单助手的 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 <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 请求 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 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();
管理副作用
将 HandlePrecognitiveRequests
中间件添加到路由时,您应该考虑在预知请求期间是否应跳过其他中间件中的任何副作用。
例如,您可能有一个中间件,用于递增每个用户与您的应用程序的总“交互”次数,但您可能不希望将预知请求计为交互。为了实现这一点,我们可以在递增交互计数之前检查请求的 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}
测试
如果您想在测试中发出预知请求,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}