Skip to content

Latest commit

 

History

History
755 lines (576 loc) · 25 KB

precognition.md

File metadata and controls

755 lines (576 loc) · 25 KB

預知

簡介

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 Precognition 套件安裝後,您現在可以使用 Precognition 的 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">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">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">
            Create User
        </button>
    </form>
</template>

現在,當使用者填寫表單時,Precognition 將提供由路由表單請求中的驗證規則驅動的即時驗證輸出。當表單的輸入更改時,將向您的 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>

Warning

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

如果您正在使用 Precognition 驗證表單輸入的子集,手動清除錯誤可能很有用。您可以使用表單的 forgetError 函式來達到此目的:

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

        form.forgetError('avatar')
    }"
>

正如我們所見,您可以連接到輸入的 change 事件並在使用者與其互動時驗證個別輸入;但是,您可能需要驗證使用者尚未互動的輸入。這在構建「嚮導」時很常見,您希望在移動到下一步之前驗證所有可見輸入,無論使用者是否與其互動。

要使用 Precognition 進行這個操作,您應該呼叫 validate 方法,將您希望驗證的欄位名稱傳遞給 only 組態鍵。您可以使用 onSuccessonValidationError 回呼來處理驗證結果:

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

當然,您也可以根據表單提交的回應執行程式碼。表單的 submit 函式會返回一個 Axios 請求承諾。這提供了一種方便的方式來存取回應有效負載,在成功提交時重置表單輸入,或處理失敗的請求:

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

        alert('User created.');
    })
    .catch(error => {
        alert('An error occurred.');
    });

您可以通過檢查表單的 processing 屬性來確定表單提交請求是否正在進行中:

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

使用 Vue 和 Inertia

Note

如果您希望在使用 Vue 和 Inertia 開發 Laravel 應用程式時有一個起步,請考慮使用我們的 起始套件 之一。Laravel 的起始套件為您的新 Laravel 應用程式提供了後端和前端驗證脚手架。

在使用 Vue 和 Inertia 前,請務必查看我們關於 使用 Vue 與 Precognition 的一般文件。在使用 Vue 與 Inertia 時,您需要透過 NPM 安裝與 Inertia 相容的 Precognition 函式庫:

npm install laravel-precognition-vue-inertia

安裝完成後,Precognition 的 useForm 函式將返回一個 Inertia 表單輔助程式,其中包含上述驗證功能。

表單輔助程式的 submit 方法已經簡化,無需指定 HTTP 方法或 URL。相反,您可以將 Inertia 的 訪問選項 作為第一個且唯一的參數傳遞。此外,submit 方法不像上面的 Vue 範例中返回一個 Promise。相反,您可以在傳遞給 submit 方法的訪問選項中提供任何 Inertia 支援的 事件回呼

<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 安裝 Laravel Precognition 的 React 前端幫助程式:

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">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">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}>
                Create User
            </button>
        </form>
    );
};

現在,當用戶填寫表單時,Precognition 將提供由路由表單請求中的驗證規則驅動的即時驗證輸出。當表單的輸入發生更改時,將向您的 Laravel 應用程序發送一個經過防抖處理的“先知”驗證請求。您可以通過調用表單的 setValidationTimeout 函式來配置防抖超時:

form.setValidationTimeout(3000);

當驗證請求正在進行時,表單的 validating 屬性將為 true

{form.validating && <div>Validating...</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>}

Warning

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

如果您正在使用 Precognition 驗證表單的部分輸入,手動清除錯誤可能很有用。您可以使用表單的 forgetError 函數來實現此功能:

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

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

正如我們所見,您可以鉤取到輸入的 blur 事件並在用戶與其互動時驗證單個輸入;但是,您可能需要驗證用戶尚未互動的輸入。這在構建“嚮導”時很常見,您希望在移動到下一步之前驗證所有可見輸入,無論用戶是否與其互動。

要使用 Precognition 實現此功能,您應該調用 validate 方法,將要驗證的字段名稱傳遞給 only 配置鍵。您可以使用 onSuccessonValidationError 回調來處理驗證結果:

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

當然,您也可以根據對表單提交的響應執行代碼。表單的 submit 函數返回一個 Axios 請求承諾。這提供了一種方便的方式來訪問響應有效載荷,在成功的表單提交上重置表單的輸入,或處理失敗的請求:

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

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

            alert('User created.');
        })
        .catch(error => {
            alert('An error occurred.');
        });
};

您可以通過檢查表單的 processing 屬性來確定表單提交請求是否正在進行中:

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

使用 React 和 Inertia

Note

如果您希望在使用 React 和 Inertia 開發 Laravel 應用程序時有一個起步,請考慮使用我們的一個 起始套件。Laravel 的起始套件為您的新 Laravel 應用程序提供了後端和前端身份驗證脚手架。

在使用 React 和 Inertia 與 Precognition 之前,請務必查看我們關於 使用 React 與 Precognition 的一般文件。當與 Inertia 一起使用 React 時,您需要通過 NPM 安裝與 Inertia 兼容的 Precognition 庫:

npm install laravel-precognition-react-inertia

安裝完成後,Precognition 的 useForm 函數將返回一個 Inertia 表單輔助器,其中包含上述討論的驗證功能。

表單輔助器的 submit 方法已經簡化,無需指定 HTTP 方法或 URL。相反,您可以將 Inertia 的 訪問選項 作為第一個且唯一的參數傳遞。此外,submit 方法不像上面的 React 示例中返回一個 Promise。相反,您可以在傳遞給 submit 方法的訪問選項中提供任何 Inertia 支持的 事件回調

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 應用程序中重複驗證規則。為了說明它的工作原理,讓我們在應用程序中建立一個用於創建新用戶的表單。

首先,要為路由啟用 Precognition,應將 HandlePrecognitiveRequests 中間件添加到路由定義中。您還應創建一個 表單請求 來存放路由的驗證規則:

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

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

接下來,您應該通過 NPM 安裝用於 Alpine 的 Laravel Precognition 前端輔助工具:

npm install laravel-precognition-alpine

然後,在您的 resources/js/app.js 文件中向 Alpine 註冊 Precognition 插件:

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

window.Alpine = Alpine;

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

安裝並註冊 Laravel Precognition 套件後,您現在可以使用 Precognition 的 $form "魔法" 創建一個表單對象,提供 HTTP 方法(post)、目標 URL(/users)和初始表單數據。

要啟用即時驗證,您應該將表單的資料綁定到相應的輸入框,然後監聽每個輸入框的 change 事件。在 change 事件處理程序中,您應該調用表單的 validate 方法,並提供輸入框的名稱:

<form x-data="{
    form: $form('post', '/register', {
        name: '',
        email: '',
    }),
}">
    @csrf
    <label for="name">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">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">
        Create User
    </button>
</form>

現在,當用戶填寫表單時,Precognition 將提供由路由表單請求中的驗證規則驅動的即時驗證輸出。當表單的輸入框發生變化時,將向您的 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>

Warning

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

正如我們所見,您可以連接到輸入框的 change 事件並在用戶與其互動時驗證個別輸入框;但是,您可能需要驗證用戶尚未互動的輸入框。這在構建“嚮導”時很常見,您希望在移至下一步之前驗證所有可見的輸入框,無論用戶是否與其互動。

要使用 Precognition 實現這一點,您應該調用 validate 方法,並將要驗證的字段名稱傳遞給 only 配置鍵。您可以使用 onSuccessonValidationError 回調來處理驗證結果:

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

您可以通過檢查表單的 processing 屬性來確定表單提交請求是否正在進行中:

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

重新填充舊表單數據

在上面討論的用戶創建示例中,我們使用 Precognition 執行實時驗證;但是,我們正在執行傳統的服務器端表單提交來提交表單。因此,表單應填充任何來自服務器端表單提交的“舊”輸入和驗證錯誤:

<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('User created.')
                })
                .catch(error => {
                    alert('An error occurred.');
                });
        },
    }"
    @submit.prevent="submit"
>

配置 Axios

Precognition 驗證庫使用 Axios HTTP 客戶端向應用程序後端發送請求。如有需要,可以自定義 Axios 實例。例如,在使用 laravel-precognition-vue 库時,您可以在應用程序的 resources/js/app.js 文件中為每個發出的請求添加額外的請求標頭:

import { client } from 'laravel-precognition-vue';

client.axios().defaults.headers.common['Authorization'] = authToken;

或者,如果您已經為應用程序配置了 Axios 實例,您可以告訴 Precognition 使用該實例:

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 風格的 Precognition 库僅在驗證請求時使用配置的 Axios 實例。表單提交將始終由 Inertia 發送。

自定義驗證規則

可以通過使用請求的 isPrecognitive 方法來自定義預知請求期間執行的驗證規則。

例如,在用戶創建表單上,我們可能希望僅在最終提交表單時驗證密碼是否“未受損”。對於預知驗證請求,我們只需驗證密碼是否必需且至少有 8 個字符。使用 isPrecognitive 方法,我們可以自定義表單請求定義的規則:

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rules\Password;

class StoreUserRequest extends FormRequest
{
    /**
     * Get the validation rules that apply to the request.
     *
     * @return array
     */
    protected function rules()
    {
        return [
            'password' => [
                'required',
                $this->isPrecognitive()
                    ? Password::min(8)
                    : Password::min(8)->uncompromised(),
            ],
            // ...
        ];
    }
}

處理檔案上傳

預設情況下,Laravel Precognition 在預知驗證請求期間不會上傳或驗證檔案。這確保大型檔案不會被多次不必要地上傳。

因此,您應確保應用程式 自訂相應表單請求的驗證規則 以指定該欄位僅在完整表單提交時為必填:

/**
 * Get the validation rules that apply to the request.
 *
 * @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
{
    /**
     * Handle an incoming request.
     */
    public function handle(Request $request, Closure $next): mixed
    {
        if (! $request->isPrecognitive()) {
            Interaction::incrementFor($request->user());
        }

        return $next($request);
    }
}

測試

如果您希望在測試中進行預知請求,Laravel 的 TestCase 包含一個 withPrecognition 助手,將添加 Precognition 請求標頭。

此外,如果您希望斷言預知請求成功,例如,沒有返回任何驗證錯誤,您可以在回應上使用 assertSuccessfulPrecognition 方法:

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