Precognition

Вступ

Laravel Precognition дозволяє передбачити результат майбутнього HTTP-запиту. Одним з основних випадків використання Precognition є можливість надавати "живу" валідацію для вашого фронтенд JavaScript-застосунку без необхідності дублювати правила валідації бекенду вашого застосунку. Precognition особливо добре поєднується з стартовими наборами на базі Inertia від Laravel.

Коли Laravel отримує "передбачуваний запит", він виконає всі middleware маршруту та вирішить залежності контролера маршруту, включаючи валідацію запитів форми - але фактично не виконуватиме метод контролера маршруту.

Жива валідація

Використання Vue

Використовуючи Laravel Precognition, ви можете запропонувати користувачам досвід живої валідації без необхідності дублювати правила валідації у вашому фронтенд-застосунку на Vue. Щоб проілюструвати, як це працює, давайте створимо форму для створення нових користувачів у нашому застосунку.

Спочатку, щоб увімкнути Precognition для маршруту, HandlePrecognitiveRequests middleware слід додати до визначення маршруту. Вам також слід створити запит форми для розміщення правил валідації маршруту:

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

Далі, вам слід встановити фронтенд-хелпери Laravel Precognition для Vue через NPM:

npm install laravel-precognition-vue

З встановленим пакетом Laravel Precognition, ви тепер можете створити об'єкт форми, використовуючи функцію Precognition useForm, вказуючи HTTP метод (post), цільовий URL (/users) та початкові дані форми.

Потім, щоб увімкнути живу валідацію, викликайте метод форми validate на події change кожного вводу, вказуючи ім'я вводу:

<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">
    Validating...
</div>

Будь-які помилки валідації, що повертаються під час запиту валідації або відправки форми, автоматично заповнять об'єкт errors форми:

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

Ви можете визначити, чи має форма якісь помилки, використовуючи властивість форми hasErrors:

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

Ви також можете визначити, чи пройшло введення перевірку, чи ні, передавши ім'я введення у функції форми valid та invalid відповідно:

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

Введення форми буде відображатися як дійсне або недійсне лише після того, як воно зміниться і буде отримано відповідь перевірки.

Якщо ви перевіряєте підмножину введених даних форми за допомогою Precognition, може бути корисним вручну очистити помилки. Ви можете використовувати функцію форми forgetError для досягнення цього:

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

Як ми бачили, ви можете підключитися до події change введення і перевіряти окремі введення, коли користувач взаємодіє з ними; однак, можливо, вам потрібно перевірити введення, з якими користувач ще не взаємодіяв. Це поширено при створенні "майстра", де ви хочете перевірити всі видимі введення, незалежно від того, чи взаємодіяв з ними користувач, перед переходом до наступного кроку.

Щоб зробити це з Precognition, ви повинні викликати метод validate, передаючи імена полів, які ви бажаєте перевірити, до ключа конфігурації only. Ви можете обробити результат валідації за допомогою зворотних викликів onSuccess або onValidationError:

<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">
    Submit
</button>

Використання Vue та Inertia

Якщо ви хочете отримати фору при розробці вашого Laravel-застосунку з Vue та Inertia, розгляньте можливість використання одного з наших стартових наборів. Стартові набори Laravel надають бекенд та фронтенд аутентифікаційні шаблони для вашого нового Laravel-застосунку.

Перш ніж використовувати Precognition з Vue та Inertia, обов'язково ознайомтеся з нашою загальною документацією щодо використання Precognition з Vue. Коли ви використовуєте Vue з Inertia, вам потрібно буде встановити сумісну з Inertia бібліотеку Precognition через NPM:

npm install laravel-precognition-vue-inertia

Після встановлення функція Precognition useForm поверне Inertia допоміжний засіб форми, доповнений функціями валідації, обговореними вище.

Метод submit хелпера форми був спрощений, усуваючи потребу вказувати HTTP метод або URL. Натомість, ви можете передати опції візиту Inertia як перший і єдиний аргумент. Крім того, метод submit не повертає Promise, як це видно у прикладі з Vue вище. Натомість, ви можете надати будь-які з підтримуваних Inertia зворотних викликів подій в опціях візиту, наданих методу submit:

<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 middleware. Також слід створити запит форми для розміщення правил валідації маршруту:

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

Далі, вам слід встановити фронтенд-хелпери Laravel Precognition для React через NPM:

npm install laravel-precognition-react

З встановленим пакетом Laravel Precognition, ви тепер можете створити об'єкт форми, використовуючи функцію Precognition useForm, вказуючи HTTP метод (post), цільовий URL (/users) та початкові дані форми.

Щоб увімкнути живу валідацію, ви повинні слухати події change та blur кожного введення. У обробнику події 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>}

Ви також можете визначити, чи пройшло введення перевірку, чи ні, передавши ім'я введення у функції форми valid та invalid відповідно:

{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, ви повинні викликати метод validate, передаючи імена полів, які ви бажаєте перевірити, до ключа конфігурації only. Ви можете обробити результат валідації за допомогою зворотних викликів onSuccess або onValidationError:

<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}>
    Submit
</button>

Використання React та Inertia

Якщо ви хочете отримати перевагу на початку розробки вашого Laravel-застосунку з React та Inertia, розгляньте можливість використання одного з наших стартових комплектів. Стартові комплекти Laravel надають бекенд та фронтенд аутентифікаційні шаблони для вашого нового Laravel-застосунку.

Перш ніж використовувати Precognition з React та Inertia, обов'язково ознайомтеся з нашою загальною документацією щодо використання Precognition з React. Коли ви використовуєте React з Inertia, вам потрібно буде встановити сумісну з Inertia бібліотеку Precognition через NPM:

npm install laravel-precognition-react-inertia

Після встановлення функція Precognition useForm поверне Inertia допоміжний об'єкт форми, доповнений функціями валідації, обговореними вище.

Метод submit хелпера форми був спрощений, усуваючи потребу вказувати HTTP метод або URL. Натомість, ви можете передати опції візиту Inertia як перший і єдиний аргумент. Крім того, метод submit не повертає Promise, як це видно в прикладі з React вище. Натомість, ви можете надати будь-які з підтримуваних Inertia зворотних викликів подій в опціях візиту, наданих методу submit:

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 middleware слід додати до визначення маршруту. Вам також слід створити запит форми для розміщення правил валідації маршруту:

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

Далі, вам слід встановити фронтенд-хелпери Laravel Precognition для Alpine через NPM:

npm install laravel-precognition-alpine

Потім зареєструйте плагін Precognition з Alpine у вашому файлі resources/js/app.js:

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>Validating...</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>

Ви також можете визначити, чи пройшло введення перевірку, чи ні, передавши ім'я введення у функції форми valid та invalid відповідно:

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

Введення форми буде відображатися як дійсне або недійсне лише після того, як воно зміниться і буде отримано відповідь перевірки.

Як ми вже бачили, ви можете підключитися до події change введення і перевіряти окремі введення, коли користувач взаємодіє з ними; однак, вам може знадобитися перевірити введення, з якими користувач ще не взаємодіяв. Це поширено при створенні "майстра", де ви хочете перевірити всі видимі введення, незалежно від того, чи взаємодіяв з ними користувач, перед переходом до наступного кроку.

Щоб зробити це з Precognition, ви повинні викликати метод validate, передаючи імена полів, які ви бажаєте перевірити, до ключа конфігурації only. Ви можете обробити результат перевірки за допомогою зворотних викликів onSuccess або onValidationError:

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

Ви можете визначити, чи запит на відправку форми знаходиться в процесі, перевіривши властивість форми processing:

<button :disabled="form.processing">
    Submit
</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 => {
                    this.form.reset();
 
                    alert('User created.')
                })
                .catch(error => {
                    alert('An error occurred.');
                });
        },
    }"
    @submit.prevent="submit"
>

Налаштування Axios

Бібліотеки валідації Precognition використовують HTTP-клієнт Axios для відправки запитів до бекенду вашого застосунку. Для зручності, екземпляр 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)

Бібліотеки Precognition зі смаком 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 Precognition не завантажує та не перевіряє файли під час запиту на передбачувальну валідацію. Це забезпечує, що великі файли не завантажуються без потреби кілька разів.

Через цю поведінку, ви повинні переконатися, що ваш застосунок налаштовує відповідні правила валідації запиту форми, щоб вказати, що поле є обов'язковим лише для повних відправлень форми:

/**
* Отримати правила валідації, які застосовуються до запиту.
*
* @return array
*/
protected function rules()
{
return [
'avatar' => [
...$this->isPrecognitive() ? [] : ['required'],
'image',
'mimes:jpg,png',
'dimensions:ratio=3/2',
],
// ...
];
}

Якщо ви хочете включити файли в кожен запит валідації, ви можете викликати функцію validateFiles на вашому екземплярі форми на стороні клієнта:

form.validateFiles();

Управління Побічними Ефектами

Коли додаєте HandlePrecognitiveRequests middleware до маршруту, слід врахувати, чи є якісь побічні ефекти в іншому middleware, які слід пропустити під час прекогнітивного запиту.

Наприклад, у вас може бути middleware, яке збільшує загальну кількість "взаємодій" кожного користувача з вашим застосунком, але ви можете не хотіти, щоб прекогнітивні запити враховувалися як взаємодія. Щоб досягти цього, ми можемо перевірити метод запиту 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's 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());
}