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>
exclamation

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

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

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

        form.forgetError('avatar')
    }"
/>

当然,您还可以在响应表单提交时执行代码。表单的 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

lightbulb

如果您希望在使用 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 for="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 for="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>;
}
exclamation

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

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

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

        form.forgetError('avatar');
    }
>

当然,您还可以在响应表单提交时执行代码。表单的 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

lightbulb

如果您希望在使用 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>
exclamation

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

您可以通过检查表单的 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()) }}),
}"
></form>

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

html
<form
  x-data="{
        form: $form('post', '/register', {
            name: '',
            email: '',
        }),
        submit() {
            this.form.submit()
                .then(response => {
                    form.reset();

                    alert('用户已创建。')
                })
                .catch(error => {
                    alert('发生错误。');
                });
        },
    }"
  @submit.prevent="submit"
></form>

配置 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);
exclamation

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
public function test_it_validates_registration_form_with_precognition()
{
    $response = $this->withPrecognition()
        ->post('/register', [
            'name' => 'Taylor Otwell',
        ]);

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