laravel
10. 软件包
预知

简介

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

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

实时验证

使用 Vue

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

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

use App\Http\Requests\StoreUserRequest;
use Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests;
 
Route::post('/users', function (StoreUserRequest $request) {
    //...
})->middleware([HandlePrecognitiveRequests::class]);

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

npm install laravel-precognition-vue

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

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

<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函数来配置防抖超时时间:

form.setValidationTimeout(3000);

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

<div v-if="form.validating">
    正在验证...
</div>

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

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

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

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

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

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

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

</span>

[!警告]
只有当表单输入发生变化并收到验证响应后,该输入才会显示为有效或无效。

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

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

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

要使用预知来实现这一点,您应该通过将您希望验证的字段的名称传递给touch方法将其标记为“已触摸”。然后,使用onSuccessonValidationError回调调用validate方法:

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

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

const submit = () => form.submit()
   .then(response => {
        form.reset();
 
        alert('用户已创建。');
    })
   .catch(error => {
        alert('发生错误。');
    });

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

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

使用 Vue 和 Inertia

[!注意]
如果您在使用 Vue 和 Inertia 开发 Laravel 应用程序时希望有一个良好的开端,可以考虑使用我们的入门套件之一。Laravel 的入门套件为您的新 Laravel 应用程序提供后端和前端身份验证框架。

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

npm install laravel-precognition-vue-inertia

安装后,预知的useForm函数将返回一个 Inertia 表单助手 (opens in a new tab),并增强了上述讨论的验证功能。

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

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

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

use App\Http\Requests\StoreUserRequest;
use Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests;
 
Route::post('/users', function (StoreUserRequest $request) {
    //...
})->middleware([HandlePrecognitiveRequests::class]);

接下来,您应该通过 NPM 为 React 安装 Laravel Precognition 前端助手:

npm install laravel-precognition-react

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

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

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>
    );
};

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

form.setValidationTimeout(3000);

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

{form.validating && <div>正在验证...</div>}

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

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

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

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

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

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

[!警告]
只有当表单输入发生变化并收到验证响应后,它才会显示为有效或无效。

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

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

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

要使用 Precognition 做到这一点,您应该通过将您希望验证的字段的名称传递给 touch 方法将其标记为“已触摸”。然后,使用 onSuccessonValidationError 回调调用 validate 方法:

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

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

const submit = (e) => {
    e.preventDefault();
 
    form.submit()
      .then(response => {
            form.reset();
 
            alert('用户已创建。');
        })
      .catch(error => {
            alert('发生错误。');
        });
};

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

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

使用 React 和 Inertia

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

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

npm install laravel-precognition-react-inertia

安装完成后,Precognition 的 useForm 函数将返回一个 Inertia 表单助手 (opens in a new tab),并增强了上述讨论的验证功能。

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

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 预认知(Precognition),您可以为用户提供实时验证体验,而无需在前端 Alpine 应用程序中重复您的验证规则。为了说明其工作原理,让我们在我们的应用程序中构建一个用于创建新用户的表单。

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

use App\Http\Requests\CreateUserRequest;
use Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests;
 
Route::post('/users', function (CreateUserRequest $request) {
    //...
})->middleware([HandlePrecognitiveRequests::class]);

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

npm install laravel-precognition-alpine

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

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

<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 函数来配置防抖超时时间:

form.setValidationTimeout(3000);

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

<template x-if="form.validating">
    <div>正在验证...</div>
</template>

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

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

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

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

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

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

[!警告]
只有当表单输入发生变化并收到验证响应后,它才会显示为有效或无效。

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

要使用预认知实现此目的,您应该通过将其名称传递给 touch 方法将您希望验证的字段标记为“已触摸”。然后,使用 onSuccessonValidationError 回调调用 validate 方法:

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

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

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

重新填充旧表单数据

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

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

或者,如果您想通过 XHR 提交表单,您可以使用表单的 submit 函数,该函数返回一个 Axios 请求承诺:

<form
    x-data="{
        form: $form('post', '/register', {
            name: '',
            email: '',
        }),
        submit() {
            this.form.submit()
               .then(response => {
                    form.reset();
 
                    alert('用户已创建。')
                })
               .catch(error => {
                    alert('发生错误。');
                });
        },
    }"
    @submit.prevent="submit"
>

配置 Axios

预认知验证库使用 Axios (opens in a new tab) HTTP 客户端向您的应用程序后端发送请求。为了方便起见,如果您的应用程序需要,可以自定义 Axios 实例。例如,当使用 laravel-precognition-vue 库时,您可以在应用程序的 resources/js/app.js 文件中为每个传出请求添加额外的请求头:

import { client } from 'laravel-precognition-vue';
 
client.axios().defaults.headers.common['Authorization'] = authToken;

或者,如果您已经为您的应用程序配置了一个 Axios 实例,您可以告诉预认知使用该实例:

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)

[!警告]
具有 Inertia 风格的预认知库将仅将配置的 Axios 实例用于验证请求。表单提交将始终由 Inertia 发送。

自定义验证规则

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

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

<?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 预认知在预认知验证请求期间不会上传或验证文件。这确保了不会不必要地多次上传大文件。

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

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

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

form.validateFiles();

管理副作用

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

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

<?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 方法:

it('使用预认知验证注册表单', function () {
    $response = $this->withPrecognition()
        ->post('/register', [
            'name' => 'Taylor Otwell',
        ]);
 
    $response->assertSuccessfulPrecognition();
 
    expect(User::count())->toBe(0);
});
public function test_it_validates_registration_form_with_precognition()
{
    $response = $this->withPrecognition()
        ->post('/register', [
            'name' => 'Taylor Otwell',
        ]);
 
    $response->assertSuccessfulPrecognition();
    $this->assertSame(0, User::count());
}