Skip to content
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待

Precognition

简介

Laravel Precognition 允许你预知未来 HTTP 请求的结果。Precognition 的主要用例之一是能够为你的前端 JavaScript 应用程序提供“实时”验证,而无需重复应用程序的后端验证规则。

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

NOTE

从 Inertia 2.3 开始,Precognition 支持已内置。更多信息请查阅 Inertia Forms 文档。较早的 Inertia 版本需要 Precognition 0.x。

实时验证

使用 Vue

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

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

php
use App\Http\Requests\StoreUserRequest;
use Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests;

Route::post('/users', function (StoreUserRequest $request) {
    // ...
})->middleware([HandlePrecognitiveRequests::class]);

接下来,你应该通过 NPM 安装适用于 Vue 的 Laravel Precognition 前端辅助库:

shell
npm install laravel-precognition-vue

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

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

vue
<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 应用程序发送一个防抖的“precognitive”验证请求。你可以通过调用表单的 setValidationTimeout 函数来配置防抖超时时间:

js
form.setValidationTimeout(3000);

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

html
<div v-if="form.validating">
    Validating...
</div>

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

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

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

html
<div v-if="form.hasErrors">
    <!-- ... -->
</div>

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

html
<span v-if="form.valid('email')">

</span>

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

</span>

WARNING

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

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

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

        form.forgetError('avatar')
    }"
>

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

要使用 Precognition 做到这一点,你应该调用 validate 方法,并将要验证的字段名称传递给 only 配置键。你可以使用 onSuccessonValidationError 回调来处理验证结果:

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

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

js
const submit = () => form.submit()
    .then(response => {
        form.reset();

        alert('User created.');
    })
    .catch(error => {
        alert('An error occurred.');
    });

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

html
<button :disabled="form.processing">
    Submit
</button>

使用 React

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

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

php
use App\Http\Requests\StoreUserRequest;
use Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests;

Route::post('/users', function (StoreUserRequest $request) {
    // ...
})->middleware([HandlePrecognitiveRequests::class]);

接下来,你应该通过 NPM 安装适用于 React 的 Laravel Precognition 前端辅助库:

shell
npm install laravel-precognition-react

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

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

jsx
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 应用程序发送一个防抖的“precognitive”验证请求。你可以通过调用表单的 setValidationTimeout 函数来配置防抖超时时间:

js
form.setValidationTimeout(3000);

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

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

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

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

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

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

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

jsx
{form.valid('email') && <span>✅</span>}

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

WARNING

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

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

jsx
<input
    id="avatar"
    type="file"
    onChange={(e) => {
        form.setData('avatar', e.target.files[0]);

        form.forgetError('avatar');
    }}
>

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

要使用 Precognition 做到这一点,你应该调用 validate 方法,并将要验证的字段名称传递给 only 配置键。你可以使用 onSuccessonValidationError 回调来处理验证结果:

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

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

js
const submit = (e) => {
    e.preventDefault();

    form.submit()
        .then(response => {
            form.reset();

            alert('User created.');
        })
        .catch(error => {
            alert('An error occurred.');
        });
};

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

html
<button disabled={form.processing}>
    Submit
</button>

使用 Alpine 和 Blade

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

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

php
use App\Http\Requests\CreateUserRequest;
use Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests;

Route::post('/users', function (CreateUserRequest $request) {
    // ...
})->middleware([HandlePrecognitiveRequests::class]);

接下来,你应该通过 NPM 安装适用于 Alpine 的 Laravel Precognition 前端辅助库:

shell
npm install laravel-precognition-alpine

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

js
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 方法,并传入输入的名称:

html
<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 应用程序发送一个防抖的“precognitive”验证请求。你可以通过调用表单的 setValidationTimeout 函数来配置防抖超时时间:

js
form.setValidationTimeout(3000);

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

html
<template x-if="form.validating">
    <div>Validating...</div>
</template>

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

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

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

html
<template x-if="form.hasErrors">
    <div><!-- ... --></div>
</template>

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

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

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

WARNING

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

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

要使用 Precognition 做到这一点,你应该调用 validate 方法,并将要验证的字段名称传递给 only 配置键。你可以使用 onSuccessonValidationError 回调来处理验证结果:

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

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

html
<button :disabled="form.processing">
    Submit
</button>

重新填充旧表单数据

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

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

或者,如果你希望通过 XHR 提交表单,你可以使用表单的 submit 函数,它返回一个 Axios 请求 promise:

html
<form
    x-data="{
        form: $form('post', '/register', {
            name: '',
            email: '',
        }),
        submit() {
            this.form.submit()
                .then(response => {
                    this.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 文件中向每个传出请求添加额外的请求标头:

js
import { client } from 'laravel-precognition-vue';

client.axios().defaults.headers.common['Authorization'] = authToken;

或者,如果你已经为应用程序配置了一个 Axios 实例,你可以告诉 Precognition 使用该实例:

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

验证数组

你可以使用通配符来验证数组或嵌套对象中的字段。每个 * 匹配单个路径段:

js
// 验证数组中所有用户的电子邮件...
form.validate('users.*.email');

// 验证 profile 对象中的所有字段...
form.validate('profile.*');

// 验证所有用户的所有字段...
form.validate('users.*.*');

自定义验证规则

可以使用请求的 isPrecognitive 方法来自定义在 precognitive 请求期间执行的验证规则。

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

php
<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rules\Password;

class StoreUserRequest extends FormRequest
{
    /**
     * 获取应用于请求的验证规则。
     *
     * @return array
     */
    protected function rules()
    {
        return [
            'password' => [
                'required',
                $this->isPrecognitive()
                    ? Password::min(8)
                    : Password::min(8)->uncompromised(),
            ],
            // ...
        ];
    }
}

处理文件上传

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

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

php
/**
 * 获取应用于请求的验证规则。
 *
 * @return array
 */
protected function rules()
{
    return [
        'avatar' => [
            ...$this->isPrecognitive() ? [] : ['required'],
            'image',
            'mimes:jpg,png',
            'dimensions:ratio=3/2',
        ],
        // ...
    ];
}

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

js
form.validateFiles();

管理副作用

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

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

php
<?php

namespace App\Http\Middleware;

use App\Facades\Interaction;
use Closure;
use Illuminate\Http\Request;

class InteractionMiddleware
{
    /**
     * 处理传入请求。
     */
    public function handle(Request $request, Closure $next): mixed
    {
        if (! $request->isPrecognitive()) {
            Interaction::incrementFor($request->user());
        }

        return $next($request);
    }
}

测试

如果你希望在测试中发起 precognitive 请求,Laravel 的 TestCase 包含一个 withPrecognition 辅助函数,它将添加 Precognition 请求标头。

此外,如果你希望断言 precognitive 请求成功,例如,没有返回任何验证错误,你可以在响应上使用 assertSuccessfulPrecognition 方法:

php
it('使用 precognition 验证注册表单', function () {
    $response = $this->withPrecognition()
        ->post('/register', [
            'name' => 'Taylor Otwell',
        ]);

    $response->assertSuccessfulPrecognition();

    expect(User::count())->toBe(0);
});
php
public function test_it_validates_registration_form_with_precognition()
{
    $response = $this->withPrecognition()
        ->post('/register', [
            'name' => 'Taylor Otwell',
        ]);

    $response->assertSuccessfulPrecognition();
    $this->assertSame(0, User::count());
}