Laravel'de Custom Validation Rule Yazma
Laravel'in built-in validation kuralları çoğu durumu karşılıyor: required, email, unique, min, max... Ama her projenin kendine özgü ihtiyaçları var. TC kimlik numarası doğrulama, IBAN kontrolü, iş günü kontrolü — bunları her controller'da elle yazmak yerine kendi validation rule'unuzu yazarsınız. Bir kez yazarsınız, her yerde kullanırsınız.
Rule Oluşturma
php artisan make:rule TcKimlikRule
Bu komut app/Rules/TcKimlikRule.php dosyasını oluşturur:
namespace App\Rules;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
class TcKimlikRule implements ValidationRule
{
public function validate(string $attribute, mixed $value, Closure $fail): void
{
// 11 haneli olmalı ve rakamlardan oluşmalı
if (!preg_match('/^\d{11}$/', $value)) {
$fail(':attribute geçerli bir TC kimlik numarası olmalıdır.');
return;
}
// İlk hane 0 olamaz
if ($value[0] === '0') {
$fail(':attribute geçerli bir TC kimlik numarası olmalıdır.');
return;
}
// Algoritma kontrolü
$digits = str_split($value);
$oddSum = $digits[0] + $digits[2] + $digits[4] + $digits[6] + $digits[8];
$evenSum = $digits[1] + $digits[3] + $digits[5] + $digits[7];
if (($oddSum * 7 - $evenSum) % 10 != $digits[9]) {
$fail(':attribute geçerli bir TC kimlik numarası olmalıdır.');
return;
}
$totalSum = 0;
for ($i = 0; $i < 10; $i++) {
$totalSum += $digits[$i];
}
if ($totalSum % 10 != $digits[10]) {
$fail(':attribute geçerli bir TC kimlik numarası olmalıdır.');
}
}
}
$fail closure'ını çağırdığınızda validation başarısız olur ve mesaj döner. :attribute placeholder'ı alan adıyla otomatik değiştirilir.
Kullanımı
Controller'da veya Form Request'te new ile instantiate edersiniz:
$request->validate([
'name' => ['required', 'string', 'max:255'],
'tc_kimlik' => ['required', new TcKimlikRule],
'email' => ['required', 'email'],
]);
Parametreli Rule
Bazı rule'lar parametre alır. Mesela yaş aralığı kontrolü:
namespace App\Rules;
use Closure;
use Illuminate\Contracts\Validation\ValidationRule;
class BirthYearRule implements ValidationRule
{
public function __construct(
private int $minYear = 1940,
private int $maxYear = 0
) {
$this->maxYear = $maxYear ?: (int) date('Y');
}
public function validate(string $attribute, mixed $value, Closure $fail): void
{
$year = (int) $value;
if ($year < $this->minYear || $year > $this->maxYear) {
$fail(":attribute {$this->minYear} ile {$this->maxYear} arasında olmalıdır.");
}
}
}
Kullanımı:
$request->validate([
'birth_year' => ['required', 'integer', new BirthYearRule(1950, 2010)],
]);
Closure Rule — Tek Seferlik Kurallar
Her kural için ayrı class oluşturmaya gerek yok. Tek yerde kullanılacak basit bir kural için closure yeterli:
$request->validate([
'username' => [
'required',
function (string $attribute, mixed $value, Closure $fail) {
if (str_contains($value, 'admin')) {
$fail(':attribute "admin" kelimesini içeremez.');
}
},
],
]);
Closure rule'lar basit kontroller için ideal. Ama aynı kuralı birden fazla yerde kullanacaksanız class'a çıkarın.
Gerçek Dünya Örnekleri
Projelerimde sıkça kullandığım custom rule'lar:
// Türk telefon numarası
class TurkishPhoneRule implements ValidationRule
{
public function validate(string $attribute, mixed $value, Closure $fail): void
{
$cleaned = preg_replace('/[\s\-\(\)]/', '', $value);
if (!preg_match('/^(\+90|0)?5\d{9}$/', $cleaned)) {
$fail(':attribute geçerli bir Türk telefon numarası olmalıdır.');
}
}
}
// IBAN kontrolü (basit format)
class IbanRule implements ValidationRule
{
public function validate(string $attribute, mixed $value, Closure $fail): void
{
$iban = strtoupper(str_replace(' ', '', $value));
if (!preg_match('/^TR\d{24}$/', $iban)) {
$fail(':attribute geçerli bir IBAN numarası olmalıdır.');
}
}
}
// İş günü kontrolü
class BusinessDayRule implements ValidationRule
{
public function validate(string $attribute, mixed $value, Closure $fail): void
{
$date = \Carbon\Carbon::parse($value);
if ($date->isWeekend()) {
$fail(':attribute bir iş günü olmalıdır.');
}
}
}
Form Request ile Kullanım
Controller'ı temiz tutmak için Form Request'e taşıyın:
namespace App\Http\Requests;
use App\Rules\TcKimlikRule;
use App\Rules\TurkishPhoneRule;
use Illuminate\Foundation\Http\FormRequest;
class StoreCustomerRequest extends FormRequest
{
public function rules(): array
{
return [
'name' => ['required', 'string', 'max:255'],
'tc_kimlik' => ['required', new TcKimlikRule],
'phone' => ['required', new TurkishPhoneRule],
'email' => ['required', 'email', 'unique:customers'],
];
}
public function messages(): array
{
return [
'tc_kimlik.required' => 'TC kimlik numarası zorunludur.',
'phone.required' => 'Telefon numarası zorunludur.',
];
}
}
Custom validation rule yazmanın güzel tarafı: bir kez yazarsınız, test edersiniz, her yerde güvenle kullanırsınız. Rule class'ı değiştiğinde tüm kullanım noktaları otomatik güncellenir. Her controller'da aynı regex'i kopyalamaktan çok daha bakım dostu.