Skip to content
赞助商赞助商
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待
虚位以待

预知

介绍

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

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

实时验证

使用 Vue

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

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

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

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

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

shell
npm install laravel-precognition-vue

安装 Laravel 预知包后,您现在可以使用预知的 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">姓名</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">电子邮件</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">
            创建用户
        </button>
    </form>
</template>

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

js
form.setValidationTimeout(3000);

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

html
<div v-if="form.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

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

如果您正在使用预知验证表单输入的子集,则可以手动清除错误。您可以使用表单的 forgetError 函数来实现这一点:

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

        form.forgetError('avatar')
    }"
>

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

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

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

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

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

        alert('用户已创建。');
    })
    .catch(error => {
        alert('发生错误。');
    });

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

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

使用 Vue 和 Inertia

NOTE

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

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

shell
npm install laravel-precognition-vue-inertia

安装后,预知的 useForm 函数将返回一个 Inertia 表单助手,并增强了上述验证功能。

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

vue
<script setup>
import { useForm } from 'laravel-precognition-vue-inertia';

const form = useForm('post', '/users', {
    name: '',
    email: '',
});

const submit = () => form.submit({
    preserveScroll: true,
    onSuccess: () => form.reset(),
});
</script>

使用 React

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

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

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

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

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

shell
npm install laravel-precognition-react

安装 Laravel 预知包后,您现在可以使用预知的 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">姓名</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">电子邮件</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}>
                创建用户
            </button>
        </form>
    );
};

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

js
form.setValidationTimeout(3000);

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

jsx
{form.validating && <div>验证中...</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

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

如果您正在使用预知验证表单输入的子集,则可以手动清除错误。您可以使用表单的 forgetError 函数来实现这一点:

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

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

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

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

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

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

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

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

            alert('用户已创建。');
        })
        .catch(error => {
            alert('发生错误。');
        });
};

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

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

使用 React 和 Inertia

NOTE

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

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

shell
npm install laravel-precognition-react-inertia

安装后,预知的 useForm 函数将返回一个 Inertia 表单助手,并增强了上述验证功能。

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

js
import { useForm } from 'laravel-precognition-react-inertia';

const form = useForm('post', '/users', {
    name: '',
    email: '',
});

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

    form.submit({
        preserveScroll: true,
        onSuccess: () => form.reset(),
    });
};

使用 Alpine 和 Blade

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

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

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

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

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

shell
npm install laravel-precognition-alpine

然后,在您的 resources/js/app.js 文件中注册预知插件:

js
import Alpine from 'alpinejs';
import Precognition from 'laravel-precognition-alpine';

window.Alpine = Alpine;

Alpine.plugin(Precognition);
Alpine.start();

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

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

html
<form x-data="{
    form: $form('post', '/register', {
        name: '',
        email: '',
    }),
}">
    @csrf
    <label for="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">电子邮件</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">
        创建用户
    </button>
</form>

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

js
form.setValidationTimeout(3000);

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

html
<template x-if="form.validating">
    <div>验证中...</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 事件并在用户与其交互时验证单个输入;然而,您可能需要验证用户尚未与之交互的输入。这在构建“向导”时很常见,您希望在移动到下一步之前验证所有可见输入,无论用户是否与其交互。

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

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

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

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

重新填充旧表单数据

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

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('用户已创建。')
                })
                .catch(error => {
                    alert('发生错误。');
                });
        },
    }"
    @submit.prevent="submit"
>

配置 Axios

预知验证库使用 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 实例,您可以告诉预知使用该实例:

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)

WARNING

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

自定义验证规则

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

例如,在用户创建表单上,我们可能希望仅在最终表单提交时验证密码是否“未泄露”。对于预知验证请求,我们将仅验证密码是必需的并且至少有 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 预知不会在预知验证请求期间上传或验证文件。这确保了大文件不会被不必要地多次上传。

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

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

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

js
form.validateFiles();

管理副作用

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

例如,您可能有一个中间件会增加每个用户与应用程序的“交互”总数,但您可能不希望预知请求被计为一次交互。为此,我们可以在增加交互计数之前检查请求的 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);
    }
}

测试

如果您希望在测试中进行预知请求,Laravel 的 TestCase 包含一个 withPrecognition 帮助程序,它将添加 Precognition 请求头。

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

php
it('validates registration form with 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());
}