· Admin

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.