Precognition
Вступ
Laravel Precognition дозволяє передбачити результат майбутнього HTTP-запиту. Одне з головних застосувань Precognition - «жива» валідація у вашому фронтенді на JavaScript без дублювання правил валідації з бекенду застосунку.
Коли Laravel отримує «прекогнітивний запит», він виконає все middleware маршруту й розв'яже залежності його контролера, зокрема провалідує запити форм, - але не виконуватиме сам метод контролера.
Починаючи з Inertia 2.3, підтримка Precognition вбудована. Докладніше читайте в документації Inertia про форми. Раніші версії Inertia потребують Precognition 0.x.
Жива валідація
Через Vue
За допомогою Laravel Precognition ви можете дати користувачам живу валідацію, не дублюючи правила валідації у своєму фронтенді на Vue. Щоб показати, як це працює, побудуймо форму створення нових користувачів у нашому застосунку.
Спершу, щоб увімкнути Precognition для маршруту, додайте до його визначення middleware HandlePrecognitiveRequests. Вам також слід створити запит форми, який міститиме правила валідації маршруту:
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 встановлено, ви можете створити об'єкт форми функцією useForm із Precognition, передавши 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 надсилатиметься відкладений (debounced) «прекогнітивний» запит валідації. Ви можете налаштувати час відкладання, викликавши функцію форми 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>
Через React
За допомогою Laravel Precognition ви можете дати користувачам живу валідацію, не дублюючи правила валідації у своєму фронтенді на React. Щоб показати, як це працює, побудуймо форму створення нових користувачів у нашому застосунку.
Спершу, щоб увімкнути Precognition для маршруту, додайте до його визначення middleware HandlePrecognitiveRequests. Вам також слід створити запит форми, який міститиме правила валідації маршруту:
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 встановлено, ви можете створити об'єкт форми функцією useForm із Precognition, передавши 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 надсилатиметься відкладений (debounced) «прекогнітивний» запит валідації. Ви можете налаштувати час відкладання, викликавши функцію форми 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.files[0]);
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>
Через Alpine і Blade
За допомогою Laravel Precognition ви можете дати користувачам живу валідацію, не дублюючи правила валідації у своєму фронтенді на Alpine. Щоб показати, як це працює, побудуймо форму створення нових користувачів у нашому застосунку.
Спершу, щоб увімкнути Precognition для маршруту, додайте до його визначення middleware HandlePrecognitiveRequests. Вам також слід створити запит форми, який міститиме правила валідації маршруту:
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 встановлено й зареєстровано, ви можете створити об'єкт форми через «магію» $form із Precognition, передавши 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 надсилатиметься відкладений (debounced) «прекогнітивний» запит валідації. Ви можете налаштувати час відкладання, викликавши функцію форми 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)
Валідація масивів
Ви можете скористатися символами підстановки, щоб валідувати поля всередині масивів чи вкладених об'єктів. Кожен * відповідає одному сегменту шляху:
// Validate email for all users in an array...
form.validate('users.*.email');
// Validate all fields in a profile object...
form.validate('profile.*');
// Validate all fields for all users...
form.validate('users.*.*');
Налаштування правил валідації
Правила валідації, які виконуються під час прекогнітивного запиту, можна налаштувати методом запиту 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();
Керування побічними ефектами
Додаючи до маршруту middleware HandlePrecognitiveRequests, подумайте, чи немає в іншому middleware побічних ефектів, які слід пропустити під час прекогнітивного запиту.
Наприклад, у вас може бути middleware, яке рахує загальну кількість «взаємодій» кожного користувача з вашим застосунком, - але ви можете не хотіти, щоб прекогнітивні запити рахувалися як взаємодія. Щоб цього досягти, перевіряйте метод запиту 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);
}
}
Тестування
Якщо ви хочете робити прекогнітивні запити у своїх тестах, TestCase у Laravel містить хелпер 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());
}