跳轉至內容

Precognition

簡介

Laravel Precognition 允許你預判未來 HTTP 請求的結果。Precognition 的主要用途之一是為前端 JavaScript 應用提供“即時”驗證,而無需重複編寫後端的驗證規則。

當 Laravel 接收到“預判請求”(precognitive request)時,它會執行路由的所有中介軟體並解析路由的控制器依賴,包括驗證 表單請求(form requests),但它實際上不會執行路由的控制器方法。

從 Inertia 2.3 開始,已內建對 Precognition 的支援。請查閱 Inertia 表單文件 以獲取更多資訊。早期的 Inertia 版本需要使用 Precognition 0.x。

即時驗證

使用 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 安裝適用於 Vue 的 Laravel Precognition 前端助手。

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>

使用 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 安裝適用於 React 的 Laravel Precognition 前端助手。

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.files[0]);
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>

使用 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 安裝適用於 Alpine 的 Laravel Precognition 前端助手。

1npm install laravel-precognition-alpine

然後,在你的 resources/js/app.js 檔案中將 Precognition 外掛註冊到 Alpine 中。

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 this.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)

驗證陣列

你可以使用萬用字元來驗證陣列或巢狀物件中的欄位。每個 * 匹配一個路徑段。

1// Validate email for all users in an array...
2form.validate('users.*.email');
3 
4// Validate all fields in a profile object...
5form.validate('profile.*');
6 
7// Validate all fields for all users...
8form.validate('users.*.*');

自定義驗證規則

可以透過在請求中使用 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}