Laravel Validation: Form Request ve Kurallar Rehberi
Laravel validation, kullanıcıdan veya bir API istemcisinden gelen verinin uygulamanın iş kurallarına uyup uymadığını denetler. Amaç yalnızca boş alanları yakalamak değildir; denetleyiciye ve veritabanına ulaşan veriyi öngörülebilir, güvenli ve işlenebilir hâle getirmektir. Bu rehberde basit $request->validate() kullanımından Form Request sınıflarına, iç içe dizilerden hata yanıtlarına ve testlere kadar sağlam bir doğrulama akışı kuracağız.
Örnekler güncel Laravel yaklaşımını temel alır. Projenizin Laravel sürümünde bir kuralın imzası farklıysa önce resmî Laravel Validation belgesini kontrol edin. Özellikle paket veya sürüm yükseltmesi yapılan projelerde doğrulama testlerini yeniden çalıştırmak, sessiz davranış değişikliklerini erken yakalar.
Laravel validation nasıl çalışır?
Gelen istek bir kurala uymadığında Laravel bir ValidationException üretir. Klasik web formunda kullanıcı önceki sayfaya yönlendirilir; hatalar ve eski giriş değerleri oturuma aktarılır. JSON bekleyen bir istekte ise alan bazlı hataları içeren 422 Unprocessable Entity yanıtı döner. Böylece aynı kural seti hem Blade formu hem de API istemcisi için kullanılabilir.

Controller içinde hızlı doğrulama
Az sayıda alanı olan tek kullanımlık bir formda doğrulamayı controller içinde tutmak yeterlidir. Kuralları dizi biçiminde yazmak, nesne tabanlı kurallar eklenince okunabilirliği korur:
public function store(Request $request): RedirectResponse
{
$validated = $request->validate([
'title' => ['required', 'string', 'max:160'],
'body' => ['required', 'string', 'min:100'],
'publish_at' => ['nullable', 'date'],
]);
$post = Post::create($validated);
return to_route('posts.show', $post);
}
nullable burada önemlidir. Laravel’in varsayılan middleware katmanı boş metinleri çoğunlukla null değerine dönüştürür. İsteğe bağlı bir alan için yalnızca date yazmak, boş bırakılan alanın geçersiz sayılmasına yol açabilir.
Doğrulanmış veriyi kullanın
Model oluştururken doğrudan $request->all() kullanmayın. Bu yöntem formda görünmeyen fakat isteğe sonradan eklenmiş alanları da uygulamaya taşıyabilir. validate(), validated() veya safe() sonucunu kullanarak yalnızca izin verdiğiniz alanlarla çalışın. Eloquent tarafındaki ilişki ve toplu atama mantığını ayrıca anlamak için Laravel Eloquent ilişkileri rehberine bakabilirsiniz.
Form Request ile kuralları controller dışına taşıma
Kurallar büyüdüğünde, birden fazla yöntem tarafından kullanıldığında veya yetkilendirme gerektirdiğinde Form Request daha temiz bir sınır oluşturur. Sınıfı şu komutla üretin:
php artisan make:request StorePostRequest
Oluşan sınıfta authorize() kullanıcının bu işlemi yapıp yapamayacağını, rules() ise verinin hangi koşulları karşılaması gerektiğini belirler:
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;
class StorePostRequest extends FormRequest
{
public function authorize(): bool
{
return $this->user()?->can('create', Post::class) ?? false;
}
public function rules(): array
{
return [
'title' => ['bail', 'required', 'string', 'max:160'],
'slug' => ['required', 'alpha_dash', Rule::unique('posts', 'slug')],
'body' => ['required', 'string', 'min:100'],
'status' => ['required', Rule::in(['draft', 'published'])],
'tags' => ['sometimes', 'array', 'max:10'],
'tags.*' => ['integer', 'exists:tags,id'],
];
}
}
bail, bir alanın ilk başarısız kuralından sonra o alandaki diğer kuralları çalıştırmaz. Bu her zaman performans çözümü değildir; asıl faydası, kullanıcıya aynı alan için gereksiz ve birbiriyle çakışan mesajlar göstermemektir.
Controller artık yalnızca uygulama akışını yönetir:
public function store(StorePostRequest $request): RedirectResponse
{
$post = Post::create($request->safe()->except('tags'));
$post->tags()->sync($request->validated('tags', []));
return to_route('posts.show', $post);
}
authorize ile validation aynı şey değildir
authorize() “Bu kullanıcı işlemi yapabilir mi?” sorusunu, rules() ise “Gönderilen veri geçerli mi?” sorusunu yanıtlar. Yetki kontrolünü yalnızca gizli bir form alanına bağlamak güvenli değildir. Policy veya Gate ile sunucu tarafında denetleyin. Proje genelindeki güvenlik başlıkları için Laravel güvenlik kontrol listesini da uygulayın.
prepareForValidation ile girdiyi hazırlama
Doğrulama öncesinde küçük ve deterministik bir normalizasyon gerekiyorsa prepareForValidation() kullanılabilir. Örneğin başlıktan slug üretmek mümkündür:
protected function prepareForValidation(): void
{
$this->merge([
'slug' => $this->slug ?: str($this->title)->slug(),
]);
}
Bu metotta veritabanına yazma, e-posta gönderme veya kuyruk başlatma gibi yan etkiler oluşturmayın. Doğrulama başarısız olabilir; bu aşama yalnızca girdiyi kurallara hazırlamalıdır.
Güncelleme kuralları ve unique güvenliği

Bir kaydı güncellerken mevcut kaydın değeri unique kontrolünden hariç tutulmalıdır. Hariç tutulacak kimliği doğrudan kullanıcı girdisinden almayın. Route model binding ile çözülmüş modeli kullanın:
public function rules(): array
{
return [
'title' => ['required', 'string', 'max:160'],
'slug' => [
'required',
'alpha_dash',
Rule::unique('posts', 'slug')->ignore($this->route('post')),
],
];
}
Resmî belge, ignore() içine kullanıcı kontrollü bir değerin doğrudan geçirilmemesi gerektiğini özellikle belirtir; aksi hâlde SQL injection riski doğabilir. Model örneği veya sistemin ürettiği güvenilir birincil anahtar kullanın.
İç içe diziler ve koşullu alanlar
Tekrarlanan form satırları için nokta gösterimi kullanılır. Aşağıdaki örnek, her ürün satırında ürün kimliği ve pozitif miktar olmasını şart koşar:
'items' => ['required', 'array', 'min:1'],
'items.*.product_id' => ['required', 'integer', 'exists:products,id'],
'items.*.quantity' => ['required', 'integer', 'min:1', 'max:100'],
sometimes alan gönderildiğinde kuralları uygular; nullable ise alanın null olmasına izin verir. Bunları birbirinin yerine kullanmayın. Bir alan başka bir değere bağlıysa required_if, required_unless veya Rule::requiredIf() gibi koşullu kuralları tercih edin.
Türkçe hata mesajları ve Blade formu
Form Request içindeki messages() yöntemi kurala özel mesaj, attributes() yöntemi ise teknik alan adının kullanıcıya nasıl gösterileceğini tanımlar:
public function messages(): array
{
return [
'title.required' => 'Yazı başlığı zorunludur.',
'tags.max' => 'En fazla :max etiket seçebilirsiniz.',
];
}
public function attributes(): array
{
return [
'publish_at' => 'yayın tarihi',
'items.*.quantity' => 'ürün miktarı',
];
}
Uygulama genelindeki çeviriler için dil dosyalarını kullanın. Dil klasörü yoksa güncel Laravel sürümünüzün desteklediği php artisan lang:publish komutuyla yayımlayabilirsiniz. Form tarafında @error ve old() yardımcıları erişilebilir bir geri bildirim üretir. Blade yapısını düzenlerken Laravel Blade template rehberi yararlı olacaktır.
<label for="title">Başlık</label>
<input
id="title"
name="title"
value="{{ old('title') }}"
aria-describedby="title-error"
@error('title') aria-invalid="true" @enderror
>
@error('title')
<p id="title-error" role="alert">{{ $message }}</p>
@enderror
API validation ve 422 hata yanıtı
İstemci Accept: application/json gönderdiğinde Laravel, validation hatalarını message ve errors alanlarıyla JSON olarak döndürür. HTTP durum kodu 422’dir. React, Vue veya mobil uygulama tarafında hata metnini ayrıştırmak yerine errors.email[0] gibi alan bazlı hataları ilgili kontrolün yanında gösterin.
API sözleşmeniz farklı bir hata zarfı gerektiriyorsa Form Request içinde failedValidation() davranışını özelleştirebilirsiniz. Ancak tüm uç noktaların aynı biçimi kullanmasına dikkat edin; aksi hâlde istemci kodunda her form için ayrı hata ayrıştırıcısı oluşur.
Laravel validation testleri nasıl yazılır?

Yalnızca kurallar dizisini okumak, gerçek HTTP davranışını doğrulamaz. Feature test ile yetkili kullanıcıyı, route model binding’i, yönlendirmeyi veya JSON hata biçimini birlikte sınayın:
public function test_title_is_required_when_creating_a_post(): void
{
$user = User::factory()->create();
$response = $this->actingAs($user)->post('/posts', [
'title' => '',
'body' => 'Yeterli uzunlukta örnek içerik.',
]);
$response->assertSessionHasErrors(['title']);
$this->assertDatabaseCount('posts', 0);
}
API testi yapıyorsanız postJson() ve assertUnprocessable() ile birlikte assertJsonValidationErrors(['title']) kullanın. Test verisini tekrar üretmek için Laravel Seeder ve Factory rehberinden, test dışındaki ağır işleri ayırmak için de Laravel Queue rehberinden
Pratik kontrol listesi
- Controller yalnızca doğrulanmış veriyi mi kullanıyor?
- Yetkilendirme, validation kuralından ayrı ve sunucu tarafında mı?
- Güncelleme isteğinde
uniquekuralı güvenilir model kimliğini mi hariç tutuyor? - İsteğe bağlı alanlarda
nullablevesometimesdoğru seçilmiş mi? - İç içe dizilerin alt anahtarları da doğrulanıyor mu?
- Blade formunda hata, eski değer ve erişilebilirlik nitelikleri bulunuyor mu?
- Web formu ile JSON isteğinin hata davranışları test edildi mi?
Sıkça Sorulan Sorular
Laravel validation controller içinde mi yazılmalı?
Küçük ve tek kullanımlık bir formda controller içindeki validate() yeterlidir. Kurallar büyüyor, tekrar kullanılıyor veya yetkilendirme içeriyorsa Form Request sınıfı daha sürdürülebilir olur.
validated ile safe arasındaki fark nedir?
validated() doğrulamadan geçen veriyi dizi olarak verir. safe() ise aynı veri üzerinde only, except ve benzeri seçici işlemler yapabileceğiniz bir kapsayıcı döndürür.
sometimes ile nullable aynı mı?
Hayır. sometimes, alan istekte varsa diğer kuralları çalıştırır. nullable, alan mevcut olduğunda değerinin null olmasına izin verir.
Laravel API validation hatası neden 422 döner?
İstek sözdizimi bakımından işlenebilir olsa da gönderilen alanlar iş kurallarını karşılamaz. Laravel JSON bekleyen doğrulama hatalarında bu durumu 422 kodu ve alan bazlı hata listesiyle bildirir.
Form Request içindeki authorize false dönerse ne olur?
İstek controller’a ulaşmadan yetkilendirme hatasıyla durur ve genellikle 403 yanıtı oluşur. Kullanıcı doğrulanmamışsa veya ilgili Policy izni yoksa bu sonuç beklenir.
Sonuç
Sağlam bir Laravel validation akışı; girdiyi normalleştirir, yetkiyi ayrı değerlendirir, yalnızca doğrulanmış veriyi iş katmanına geçirir ve hata davranışını testlerle güvenceye alır. Basit formlarda $request->validate() ile başlayın; kurallar büyüdüğünde Form Request’e geçin. Özellikle unique, iç içe diziler, isteğe bağlı alanlar ve API 422 yanıtları için hem başarılı hem başarısız senaryoları test edin.




