Laravel Precognition: Validation Real-Time Không Cần Lặp Lại Rules

· 6 min read

Form validation là đoạn code bị duplicate nhiều nhất trong lập trình web. Bạn viết rules trong Laravel, rồi viết lại bằng JavaScript. Khi rules thay đổi, phải update cả hai — hoặc quên và tạo ra bugs.

Laravel Precognition giải quyết bằng cách cho frontend hỏi backend để validate từng field real-time, tái sử dụng rules từ Form Request sẵn có.

Cơ Chế Hoạt Động

User gõ email → Frontend gửi partial request → Backend validate CHỈ field đó
                                              → Trả kết quả validation
                                              → Frontend hiển thị lỗi ngay lập tức

Không submit form. Không reload trang. Không duplicate rules.

Cài Đặt

Backend

composer require laravel/precognition

Frontend (chọn stack)

# Vue
npm install laravel-precognition-vue

# React
npm install laravel-precognition-react

# Alpine.js
npm install laravel-precognition-alpine

Thiết Lập Backend

1. Thêm Middleware

// routes/web.php
Route::post('/register', [RegistrationController::class, 'store'])
    ->middleware('precognitive');

Middleware precognitive nói với Laravel: "Nếu đây là precognitive request, validate và trả về ngay — không chạy logic controller."

2. Dùng Form Request (Bạn Chắc Đã Có Rồi)

// app/Http/Requests/RegisterRequest.php

namespace App\Http\Requests;

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

class RegisterRequest extends FormRequest
{
    public function rules(): array
    {
        return [
            'name' => ['required', 'string', 'max:255'],
            'email' => ['required', 'email', 'unique:users,email'],
            'username' => ['required', 'string', 'min:3', 'max:30', 'unique:users,username', 'alpha_dash'],
            'password' => ['required', 'confirmed', Password::min(8)->mixedCase()->numbers()],
        ];
    }

    public function messages(): array
    {
        return [
            'username.unique' => 'Username này đã được sử dụng.',
            'email.unique' => 'Đã có tài khoản với email này.',
        ];
    }
}

3. Dùng Trong Controller

class RegistrationController extends Controller
{
    public function store(RegisterRequest $request)
    {
        // Code này CHỈ chạy khi submit thật, không phải precognitive request
        $user = User::create($request->validated());
        Auth::login($user);
        return redirect('/dashboard');
    }
}

Xong phần backend. Form Request hiện tại hoạt động nguyên trạng.

Frontend: Vue 3

<script setup>
import { useForm } from 'laravel-precognition-vue';

const form = useForm('post', '/register', {
    name: '',
    email: '',
    username: '',
    password: '',
    password_confirmation: '',
});

const submit = () => form.submit({
    onSuccess: () => window.location.href = '/dashboard',
});
</script>

<template>
    <form @submit.prevent="submit">
        <div>
            <label>Email</label>
            <input v-model="form.email" @change="form.validate('email')" type="email" />
            <span v-if="form.invalid('email')" class="text-red-500">{{ form.errors.email }}</span>
            <span v-if="form.valid('email')" class="text-green-500">✓ Khả dụng</span>
        </div>

        <div>
            <label>Username</label>
            <input v-model="form.username" @change="form.validate('username')" />
            <span v-if="form.invalid('username')" class="text-red-500">{{ form.errors.username }}</span>
        </div>

        <div>
            <label>Password</label>
            <input v-model="form.password" @change="form.validate('password')" type="password" />
            <span v-if="form.invalid('password')" class="text-red-500">{{ form.errors.password }}</span>
        </div>

        <button :disabled="form.processing" type="submit">Đăng Ký</button>
    </form>
</template>

Điểm chính:

  • @change="form.validate('email')" — trigger validation cho field đó khi user rời input
  • form.invalid('email') — kiểm tra field có lỗi không
  • form.valid('email') — kiểm tra field đã validated và passed
  • Form tự động debounce

Frontend: Alpine.js

Cho ứng dụng Blade-first:

<form
    x-data="{
        form: $form('post', '/register', {
            name: '', email: '', username: '',
            password: '', password_confirmation: '',
        })
    }"
    @submit.prevent="form.submit()"
>
    <div>
        <label>Email</label>
        <input x-model="form.email" @change="form.validate('email')" type="email">
        <template x-if="form.invalid('email')">
            <p x-text="form.errors.email" class="text-red-500 text-sm"></p>
        </template>
        <template x-if="form.valid('email')">
            <p class="text-green-500 text-sm">✓ Khả dụng</p>
        </template>
    </div>

    <button :disabled="form.processing" type="submit">Đăng Ký</button>
</form>

Xử Lý Side Effects

Đôi khi validation có side effects cần skip khi precognitive:

class StoreOrderRequest extends FormRequest
{
    protected function prepareForValidation(): void
    {
        if ($this->isPrecognitive()) {
            return; // Skip khi precognitive validation
        }

        $this->merge(['discount' => $this->calculateDiscount()]);
    }
}

File Uploads

Precognition hỗ trợ validate file:

public function rules(): array
{
    return [
        'avatar' => ['required', 'image', 'max:2048', 'dimensions:min_width=100,min_height=100'],
    ];
}

File được gửi lên backend để validate — size, dimensions, MIME type — tất cả check server-side.

Performance

Mỗi lần validate field là một HTTP request. Tối ưu:

  1. Dùng @change thay vì @input cho rules nặng (như unique)
  2. Debounce phù hợp — 500ms khi gõ, 0ms cho dropdowns
  3. Nhóm validations liên quan:
form.validate(['password', 'password_confirmation']);

Testing

public function test_precognitive_validation_for_email(): void
{
    User::factory()->create(['email' => 'taken@example.com']);

    $this->post('/register', ['email' => 'taken@example.com'], [
        'Precognition' => 'true',
        'Precognition-Validate-Only' => 'email',
    ])->assertStatus(422)
      ->assertJsonValidationErrors('email');
}

Kết Luận

Laravel Precognition cho bạn real-time form validation với zero rule duplication. Form Requests là single source of truth.

Khi nào nên dùng:

  • Form đăng ký/signup với uniqueness checks
  • Multi-step forms cần validate từng bước
  • Form phức tạp với conditional rules

Khi nào không nên dùng:

  • Form đơn giản chỉ cần check client-side (required, min length)
  • Form hiếm khi fail validation
  • Form traffic cao mà server load là concern

Bình luận