Laravel menyediakan puluhan validation rule
bawaan, dari email sampai exclude_if. Tetapi kebutuhan domain selalu menemukan celah: kode voucher yang harus cocok dengan pola internal, komentar yang tidak boleh memuat kata terlarang, atau kode produk yang wajib kapital. Menulis rule sendiri tidak berarti meninggalkan sistem validasi — kita hanya menambah method ke dalamnya, lengkap dengan pesan error, integrasi ke Validator::make(), dan FormRequest seperti biasa. Di chapter ini kita naik tangga bertahap: mulai dari closure satu kali pakai, lalu rule class yang bisa dipakai ulang, sampai rule berparameter yang bisa diuji sendiri.
Rule sebagai Closure #
Untuk aturan yang hanya berlaku di satu tempat, closure adalah bentuk paling ringkas. Closure menerima tiga parameter: nama atribut, nilai yang divalidasi, dan closure $fail untuk melaporkan kegagalan:
<?php
use Illuminate\Support\Facades\Validator;
$validator = Validator::make(
['judul' => 'Promosi murah, cek http://joki.example.com ya'],
['judul' => ['required', 'string', 'max:100',
function (string $attribute, mixed $value, Closure $fail): void {
if (str_contains($value, 'http')) {
$fail('Kolom :attribute tidak boleh memuat tautan.');
}
},
]],
);
foreach ($validator->errors()->all() as $pesan) {
echo $pesan.PHP_EOL;
}Output yang Dihasilkan:
Kolom judul tidak boleh memuat tautan.Placeholder :attribute otomatis diganti nama field. Kekurangan closure: logikanya menempel di tempat pemakaian dan tidak bisa diuji terpisah. Begitu aturan yang sama muncul di form kedua, saatnya pindah ke class.
Rule sebagai Class Invokable #
Pola rule class adalah bagian resmi dari dokumentasi custom validation rules , dan generator menyiapkan kerangkanya:
php artisan make:rule UppercaseOutput yang Dihasilkan:
INFO Rule [app/Rules/Uppercase.php] created successfully.Isi kelasnya mengikuti contract ValidationRule dengan satu method validate():
<?php
namespace App\Rules;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Translation\PotentiallyTranslatedString;
class Uppercase implements ValidationRule
{
public function validate(string $attribute, mixed $value, Closure $fail): void
{
if (mb_strtoupper($value, 'UTF-8') !== $value) {
$fail('Kolom :attribute harus berisi huruf kapital semua.');
}
}
}Method validate() tidak mengembalikan boolean. Kita hanya memanggil $fail() ketika nilai tidak lolos — jika method selesai tanpa memanggil $fail(), nilai dianggap sah. Pemakaiannya tinggal menyisipkan instance rule ke array rules:
$validator = Validator::make(
['kode' => 'Laravel'],
['kode' => ['required', 'string', new Uppercase]],
);
echo $validator->errors()->first('kode');Output yang Dihasilkan:
Kolom kode harus berisi huruf kapital semua.Rule Berparameter #
Aturan yang butuh konfigurasi menerima parameter lewat constructor. Rule KataTerlarang menyimpan daftar kata yang dilarang, lalu memeriksanya satu per satu:
<?php
namespace App\Rules;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Translation\PotentiallyTranslatedString;
class KataTerlarang implements ValidationRule
{
/**
* @param list<string> $kata
*/
public function __construct(protected array $kata)
{
}
public function validate(string $attribute, mixed $value, Closure $fail): void
{
if ($value === null || $value === '') {
return; // nilai kosong diserahkan ke rule required
}
$nilai = mb_strtolower((string) $value);
foreach ($this->kata as $kata) {
if (str_contains($nilai, $kata)) {
$fail("Kolom :attribute mengandung kata terlarang \"{$kata}\".");
}
}
}
}Perhatikan baris if ($value === null || $value === '') { return; } — logika bersyarat di dalam rule. Field opsional seperti komentar boleh kosong; memeriksa string kosong bukan tugas KataTerlarang melainkan rule required yang kita letakkan terpisah. Dengan pemisahan ini, satu rule tidak mencuri tanggung jawab rule lain. Pemakaiannya:
$validator = Validator::make(
['komentar' => 'Framework ini sangat lelet'],
['komentar' => ['string', new KataTerlarang(['lelet', 'jelek'])]],
);
echo $validator->errors()->first('komentar');Output yang Dihasilkan:
Kolom komentar mengandung kata terlarang "lelet".Pesan Multibahasa #
Ketika aplikasi mendukung beberapa bahasa, pesan hardcode di $fail() menjadi penghalang. Laravel mengizinkan $fail() menerima language key yang diterjemahkan lewat ->translate(). Pertama, publish berkas bahasa bawaan:
php artisan lang:publishOutput yang Dihasilkan:
INFO Language files published successfully.Lalu tambahkan kunci pesan ke lang/en/validation.php:
'uppercase' => 'The :attribute field must be uppercase.',Sekarang rule cukup mengganti isi $fail() menjadi $fail('validation.uppercase')->translate() — hasil validasi yang sama kini membaca pesan dari berkas bahasa:
<?php
$validator = Validator::make(
['kode' => 'Laravel'],
['kode' => ['required', 'string', new Uppercase]],
);
echo $validator->errors()->first('kode');Output yang Dihasilkan:
The kode field must be uppercase.Ketika locale berubah, pesan ikut berubah tanpa menyentuh kode rule — sama seperti pesan rule bawaan.
Menguji Rule Secara Langsung #
Rule class adalah PHP biasa, jadi bisa diuji tanpa HTTP — cukup Validator::make() dan assertion pada hasilnya. Berikut empat test yang menguji jalur lolos, gagal, berparameter, dan jebakan field hilang:
Output yang Dihasilkan:
PASS Tests\Feature\CustomRulesTest
✓ nilai kapital meloloskan uppercase rule 0.08s
✓ nilai campuran gagal dengan pesan kustom 0.01s
✓ kata terlarang dengan parameter 0.01s
✓ rule tidak dijalankan pada field yang hilang 0.01s
Tests: 4 passed (6 assertions)
Duration: 0.18sTest pertama dan terakhir layak diperhatikan bersama-sama. Test pertama memastikan nilai LARAVEL lolos tanpa pesan error, sementara test terakhir membuktikan peringatan di callout di atas: field yang hilang dianggap lolos. Dua test ini menjaga kita dari dua kesalahan berlawanan — rule terlalu ketat pada data yang sah, dan rule yang terlalu longgar pada input yang jahat.
Validasi menjaga kualitas data yang masuk ke aplikasi. Bab berikutnya menjaga kualitas data di sisi lain: cara nilai disimpan dan dibaca dari database, lewat custom cast yang mengubah kolom mentah menjadi objek bermakna.