跳到主要內容

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 <input
16 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 <input
26 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 User
37 </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>

您也可以通過將輸入框的名稱傳遞給表單的 validinvalid 函數,分別判斷輸入框是否通過或未通過驗證

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<input
2 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 設定鍵。您可以使用 onSuccessonValidationError 回調函數處理驗證結果

1<button
2 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 Submit
3</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) 和初始表單資料。

要啟用即時驗證,您應該監聽每個輸入框的 changeblur 事件。在 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 <input
19 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 <input
28 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 User
37 </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>}

您也可以通過將輸入框的名稱傳遞給表單的 validinvalid 函數,分別判斷輸入框是否通過或未通過驗證

1{form.valid('email') && <span></span>}
2 
3{form.invalid('email') && <span></span>}

表單輸入框只有在更改且收到驗證回應後才會顯示為有效或無效。

如果您正在使用 Precognition 驗證表單輸入框的子集,則手動清除錯誤可能會很有用。您可以使用表單的 forgetError 函數來實現此目的

1<input
2 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 設定鍵。您可以使用 onSuccessonValidationError 回調函數處理驗證結果

1<button
2 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 Submit
3</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 <input
10 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 <input
21 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 User
32 </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>

您也可以通過將輸入框的名稱傳遞給表單的 validinvalid 函數,分別判斷輸入框是否通過或未通過驗證

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 設定鍵。您可以使用 onSuccessonValidationError 回調函數處理驗證結果

1<button
2 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 Submit
3</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 array
14 */
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 InteractionMiddleware
10{
11 /**
12 * Handle an incoming request.
13 */
14 public function handle(Request $request, Closure $next): mixed
15 {
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}