Semua contoh sejauh ini menulis rules menempel di method controller. Pendekatan itu terasa wajar saat rules-nya lima baris — tetapi bayangkan rules pendaftaran pengguna yang lengkap: nama, email, password, konfirmasi, telepon, alamat. Ditambah lagi, route store dan update sering butuh rules yang mirip. Ketika kode validasi mulai menelan separuh method controller, waktunya memindahkannya ke class tersendiri. Laravel menyediakan FormRequest: class request khusus yang membawa rules, otorisasi, dan persiapan data dalam satu berkas terpisah yang bisa diuji dan dipakai ulang.

Membuat FormRequest dengan Artisan #

Kita buat FormRequest untuk menyimpan post lewat perintah artisan:

bash
php artisan make:request StorePostRequest

Output yang Dihasilkan:

text

   INFO  Request [app/Http/Requests/StorePostRequest.php] created successfully.  

Perintah tersebut menghasilkan berkas app/Http/Requests/StorePostRequest.php dengan kerangka berikut — seluruhnya hasil asli generator, tanpa satu baris pun yang kita tulis:

php
<?php

namespace App\Http\Requests;

use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Foundation\Http\FormRequest;

class StorePostRequest extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     */
    public function authorize(): bool
    {
        return false;
    }

    /**
     * Get the validation rules that apply to the request.
     *
     * @return array<string, ValidationRule|array<mixed>|string>
     */
    public function rules(): array
    {
        return [
            //
        ];
    }
}

Dua method inilah jantung FormRequest. Method authorize() menjawab pertanyaan “bolehkah pengguna ini menjalankan request tersebut?” — diisi logika otorisasi seperti memeriksa peran pengguna. Method rules() mengembalikan array rules seperti yang biasa kita tulis di controller. Perhatikan bahwa authorize() bawaan mengembalikan false; kita wajib mengubahnya ke true (atau ke kondisi yang benar), sebab nilai false membuat semua request ditolak dengan status 403.

Mengisi authorize(), rules(), dan prepareForValidation() #

Berikut StorePostRequest yang sudah kita isi lengkap. Rules memakai gaya array agar mudah ditambah objek rule nantinya:

php
<?php

namespace App\Http\Requests;

use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Foundation\Http\FormRequest;

class StorePostRequest extends FormRequest
{
    public function authorize(): bool
    {
        return true;
    }

    /**
     * @return array<string, ValidationRule|array<mixed>|string>
     */
    public function rules(): array
    {
        return [
            'judul' => ['required', 'string', 'min:5', 'max:100'],
            'isi' => ['required', 'string'],
            'email' => ['required', 'email'],
        ];
    }

    protected function prepareForValidation(): void
    {
        $this->merge([
            'judul' => trim((string) $this->input('judul')),
        ]);
    }
}

Tiga bagian ini bekerja pada fase yang berbeda. authorize() dievaluasi sebelum validasi — jika mengembalikan false, Laravel langsung menolak request dengan 403 tanpa memeriksa rules sama sekali. rules() dijalankan tepat setelahnya untuk memvalidasi data. Method prepareForValidation() — opsional — berjalan paling awal, memberi kita kesempatan merapikan input mentah sebelum diperiksa: memangkas spasi, menyeragamkan huruf, atau menggabungkan nilai default.

Type-Hinting di Controller #

Penggunaan di controller hanya soal mengganti type-hint parameter. Laravel secara otomatis mem-build class request, menjalankan authorize() dan rules(), lalu menyuntikkan instance-nya:

php
<?php

namespace App\Http\Controllers;

use App\Http\Requests\StorePostRequest;

class PostFormRequestController extends Controller
{
    public function store(StorePostRequest $request)
    {
        $validated = $request->validated();

        return response()->json($validated);
    }
}

Method store kini ramping: seluruh pemeriksaan sudah selesai sebelum baris pertamanya tereksekusi. Jika validasi gagal, exception dilempar otomatis dan baris $validated = ... tidak pernah dijalankan — perilaku yang sama dengan validate() inline di chapter sebelumnya. Saat validasi gagal di authorize() sekalipun, bentuk penolakannya berbeda: HTTP 403, bukan redirect berisi error bag, karena ini soal izin bukan soal data. Route-nya sendiri biasa saja — tidak ada middleware tambahan untuk mengaktifkan FormRequest:

php
use App\Http\Controllers\PostFormRequestController;

Route::post('/form-request/posts', [PostFormRequestController::class, 'store'])
    ->name('form-request.posts.store');

Keuntungan terbesar pendekatan ini muncul ketika rules tumbuh: class request bisa diuji terpisah dari controller, dipakai ulang di beberapa route, dan membawa logika persiapan data di satu tempat yang jelas pemiliknya.

Menguji Route Berbasis FormRequest #

Feature test membuktikan seluruh alur tersebut. Pertama, input valid yang sengaja diberi spasi berlebih di judul:

php
public function test_valid_input_passes_through_form_request(): void
{
    $response = $this->postJson('/form-request/posts', [
        'judul' => '  Belajar Form Request  ',
        'isi' => 'Isi tulisan via form request.',
        'email' => '[email protected]',
    ]);

    $response->assertStatus(200);
}

Output yang Dihasilkan:

text
status: 200
body : {"judul":"Belajar Form Request","isi":"Isi tulisan via form request.","email":"[email protected]"}

Judul yang dikirim ' Belajar Form Request ' sudah tampil terpangkas sebagai 'Belajar Form Request' — bukti prepareForValidation() benar-benar berjalan sebelum rules memeriksa nilai. Kedua, input tidak valid:

php
public function test_invalid_input_fails_before_controller_runs(): void
{
    $response = $this->postJson('/form-request/posts', [
        'judul' => 'pendek',
        'isi' => '',
        'email' => 'bukan-email',
    ]);

    $response->assertStatus(422);
}

Output yang Dihasilkan:

text
status: 422
{"message":"The isi field is required. (and 1 more error)","errors":{"isi":["The isi field is required."],"email":["The email field must be a valid email address."]}}

Response 422 terbentuk persis seperti validasi inline — konsumen API tidak melihat perbedaan apa pun. Yang berubah hanyalah struktur internal kode kita.

Selain authorize(), rules(), dan prepareForValidation(), FormRequest masih menyimpan dua hook yang sering dipakai: messages() untuk pesan error kustom dan attributes() untuk nama field yang ramah dibaca. Keduanya akan kita bedah tuntas di chapter berikutnya, lengkap dengan alternatifnya lewat file bahasa. Selengkapnya bisa dipelajari di dokumentasi Validation — Form Requests .