Form Doğrulama: İstemci ve Sunucu
Dokuz alanlı bir kayıt formunda çift katmanlı doğrulama: kurallar tek kaynakta tanımlanır, hem JavaScript hem PHP aynı listeden okur. Canlı geri bildirim ve şifre gücü ölçer dahil.
Ekran Görüntüleri 1 görsel
Bu Örnek Ne Yapıyor?
- Dokuz alanlı kayıt formu; her alan hem tarayıcıda hem sunucuda doğrulanıyor
- Kurallar system/rules.php içinde bir kez VERİ olarak tanımlı — iki taraf aynı listeden okuyor
- İstemci doğrulaması atlandığında sunucunun 422 döndürdüğü ve kayıt oluşmadığı ölçülmüş
- Kullanıcı adı ve e-posta için yazarken canlı benzersizlik kontrolü (Ajax)
- Şifre gücü ölçer ve bcrypt'in 72 karakter sınırı gibi gerçek sınırların işlenmesi
- İki listenin ayrışmasıyla oluşan altı somut hata tablo hâlinde ölçülüp belgelenmiş
- TOCTOU zinciri: "müsait" cevabı ile kaydın yazılması arasındaki yarış koşulu ele alınmış
Nerede İşe Yarar?
- Üyelik ve kayıt formlarında kullanıcı adı / e-posta benzersizliğinin kontrolü
- Var olan bir projede istemci ve sunucu kurallarının ayrışmasının giderilmesi
- Şifre politikası (uzunluk, büyük/küçük harf, rakam) uygulanan her form
- Ekip içi eğitimde "tarayıcıdaki kontrol neden güvenlik değildir" örneği
- Çok alanlı başvuru ve sipariş formlarında alan bazlı hata gösterimi
Gereksinimler
- PHP 8.0+ · MySQL 5.7+ veya MariaDB 10.3+ · Apache/mod_rewrite · Tarayıcıda JavaScript
Nasıl Kurulur?
- Proje dosyalarını web köküne kopyalayın (örn. XAMPP'ta htdocs veya kendi web sunucunuzun kök dizini)
- cy_validation.sql dosyasını içe aktarın — kullanıcı tablosunu ve benzersiz indeksleri oluşturur
- system/config.php içindeki DB_HOST, DB_NAME, DB_USER ve DB_PASS değerlerini düzenleyin
- Formu açıp bir alana yanlış veri yazarak canlı geri bildirimi deneyin
- İsterseniz JavaScript'i kapatıp formu gönderin: sunucu aynı kuralları tek başına uygular
Veritabanı şeması projedeki cy_validation.sql dosyasında.
Nasıl Çalışıyor?
Form Doğrulama Örneği
İstemci + sunucu çift katmanlı form doğrulama — kurallar tek kaynaktan. Canlı geri bildirim · Şifre gücü ölçer · AJAX benzersizlik kontrolü · TOCTOU zinciricilginyazilim.com · Türkçe | English
Tek karede projenin tamamı: e-posta canlı olarak sorulup “zaten kayıtlı” dönmüş,
kullanıcı adının altında yeşil “✓ Müsait”, şifre ölçeri “Çok Güçlü”, telefon ve doğum tarihi
kırmızı ve gerekçeli. Form, siz yazarken cevap veriyor.
Bu proje ne yapıyor?
Dokuz alanlı bir kayıt formu. Her alan hem anında (JavaScript, kullanıcı deneyimi için) hem de sunucuda (PHP, güvenlik sınırı olarak) doğrulanır. Kullanıcı adı ve e-posta için “müsait mi?” sorusu, yazarken CANLI olarak sorulur.
Ama projenin anlattığı asıl şey bir form değil, formun altındaki üç soru:
1. İstemci doğrulaması neden güvenlik değildir, sunucu doğrulaması neden tek sınırdır?
2. Aynı kural iki dilde iki kez yazıldığında ne olur — ve bu yapısal olarak nasıl çözülür?
3. “Bu kullanıcı adı müsait” cevabı ne zaman yalan olur, ve bu yalan hangi katmanda yakalanır?
1. Altın kural: istemci doğrulaması güvenlik değildir
Tarayıcıdaki hiçbir kontrol güvenlik önlemi değildir.
Bu bir slogan değil, ölçülebilir bir gerçektir. Tarayıcı konsolunu açıp CyValidation nesnesini silebilir, assets/js/validation.js dosyasını hiç yüklemeyebilir ya da formu hiç açmadan doğrudan system/ajax.php'ye istek atabilirsiniz. Bu depoda tam olarak bu yapıldı — JavaScript hiç çalıştırılmadan, kabuktan doğrudan POST edildi:
POST system/ajax.php
full_name=x email=gecersiz username=1KOTU
password=kisa password_confirm=baska
birth_date=2030-13-45 terms=0
→ HTTP 422
errors: full_name, email, username, password,
password_confirm, birth_date, terms
→ Oluşan kayıt: 0İstemci tarafı ne için var öyleyse? Kullanıcının hatayı görmek için sayfanın yenilenmesini beklememesi için. Tek işlevi budur ve bu değersiz bir işlev değildir — ama güvenlikle hiçbir ilgisi yoktur.
Sunucu tarafı neden tek sınırdır? Çünkü saldırganın kontrol edemediği tek yer orasıdır. Tarayıcıdaki kod kullanıcının makinesinde, kullanıcının denetiminde çalışır; kural koyduğunuz yer, kuralı uygulayan taraf değildir.
2. İki dilde tek kural: ayrışma sorunu ve yapısal çözümü
Bu projenin en öğretici mühendislik kararı burasıdır.
Sorun ölçüldü
Eski sürümde her JavaScript doğrulayıcısının üstünde// bkz. function.php validate_email() gibi bir yorum vardı. Yani “bu iki liste aynı olmalı” yorumla söylenmişti. Yorum derlenmez, test edilmez, kırılmaz. Sonuç, gerçek formu tarayıcıda sürerek ölçüldü:
| Girdi | Sunucu | İstemci | Sonuç |
|---|---|---|---|
| E-posta, 191 karakter | ❌ reddetti | ✅ kabul etti | Kullanıcı formu gönderiyor, sunucudan hata yiyor |
| Şifre, 73 karakter | ❌ reddetti | ✅ kabul etti | Aynı |
| Mesaj, 495 harf + 20 boşluk | ✅ kabul etti | ❌ reddetti | Geçerli girdi istemcide engelleniyor |
| Ad soyad, 60 astral harf | ✅ kabul etti | ❌ reddetti | Aynı (.length yüzeyde 120 sayıyor) |
Kullanıcı adı "şş" | “desen hatası” | “uzunluk hatası” | Aynı girdiye iki farklı gerekçe |
Kullanıcı adı "şşşşşşşşşşş" | “uzunluk hatası” | “desen hatası” | Aynı, ters yönde |
Bu iki sınırın (190 / 72) istemcide hiç yazılmamış olması bir dikkatsizlik değil, kaçınılmaz bir sonuçtur: aynı bilgi iki yerde tutulduğu her yerde, zamanla ayrışır.
Çözüm: sınırları VERİ hâline getirmek
Sayısal sınırlar, desenler ve hata mesajları artıksystem/rules.php içinde bir kez tanımlanır:
'password' => [
'required' => true,
'min' => 8,
'max' => 72, // bcrypt'in sert sınırı
'pattern' => '^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$',
'messages' => [
'length' => 'Şifre en az {min} karakter olmalıdır.',
'max' => 'Şifre en fazla {max} karakter olabilir.',
'pattern' => 'Şifre en az bir büyük harf, bir küçük harf ve bir rakam içermelidir.',
],
],- PHP doğrulayıcıları bu diziyi
rule_check()üzerinden okur. index.phpaynı diziyiclient_rules()ile JSON'a çevirip sayfaya gömer.validation.jso JSON'u okur. Dosyada72,190,100gibi tek bir sayı bile yazılı değildir.- HTML
maxlengthdeğerleri de aynı diziden basılır — yani aynı sınırın üçüncü bir elle yazılmış kopyası da yok.
Bağlantının kanıtı
Yorumla “aynı olmalı” demek yetmediği için, bağın gerçekten var olduğu ölçüldü.rules.php'de iki sayı değiştirildi (full_name.max 100 → 40, birth_date.min_age 18 → 21) ve başka hiçbir dosyaya dokunulmadı:
| Önce | Sonra | |
|---|---|---|
| Sunucu – 50 harflik ad | kabul | Ad soyad 2-40 karakter arasında olmalıdır. |
| İstemci – 100 harflik ad | kabul | Ad soyad 2-40 karakter arasında olmalıdır. |
| Sunucu – 19 yaşındaki tarih | kabul | …en az 21 yaşında olmalısınız. |
| İstemci – 17 yaşındaki tarih | …en az 18… | …en az 21 yaşında olmalısınız. |
Yalnızca sayı değil, mesaj cümlesi de birlikte hareket etti — çünkü mesajlar da {min} / {max} / {age} yer tutucularıyla aynı kaynaktan üretiliyor.
Bu çözümün kapsamadığı şey (dürüst sınır)
Yalnızca veriye dönüştürülebilen kısım paylaşılır. Şunlar her dilde ayrı yazılmak zorundadır ve bilinçli olarak ayrı bırakılmıştır:| Yordam | PHP | JavaScript | Neden paylaşılamaz |
|---|---|---|---|
| E-posta biçimi | filter_var(FILTER_VALIDATE_EMAIL) | basit desen | Tarayıcıda birebir karşılığı yok |
| Yaş hesabı | DateTimeImmutable::diff() | elle hesap | Farklı tarih kitaplıkları |
| Telefon normalleştirme | preg_replace + substr | replace + slice | Aynı mantık, farklı sözdizimi |
E-posta deseni istemcide bilerek daha gevşektir. Gevşek istemci, sunucunun kabul edeceği bir adresi reddetme hatasına düşmez; ters yön ise yalnızca fazladan bir sunucu turudur. Yani ayrışmanın hangi yöne olabileceği de bir tasarım kararıdır.
Paylaşılan desenler ayrıca iki motorda da aynı anlama gelen alt kümeyle sınırlıdır (\p{L}, \p{M}, \d, \s, karakter sınıfları, çapalar). PHP'ye özgü hiçbir şey kullanılmaz — kullanılsaydı desen tarayıcıda sessizce farklı davranır ve çözmeye çalıştığımız sorun geri gelirdi.
3. “Müsait mi?” — UX değeri ile sayım açığı arasındaki ödünleşim
Kullanıcı adı/e-posta yazarken 500 ms geciktirmeli bir AJAX isteği “müsait mi?” diye sorar. Bu özelliğin iki yüzü vardır ve bu depo ikisini de ölçtü.
İyi yüzü: gerçek bir UX kazancı
Kullanıcı, formu doldurup gönderdikten sonra “bu kullanıcı adı alınmış, baştan seç” duvarına çarpmaz. Cevabı yazarken alır. Ekran görüntüsündeki yeşil “✓ Müsait” budur.Kötü yüzü: sayım (enumeration) açığı
Bu uç nokta, tanımı gereği “bu e-posta bu sitede kayıtlı mı?” sorusunu yanıtlar. Elinde e-posta listesi olan biri, listeyi tek tek sorup hangi adreslerin kayıtlı olduğunu öğrenebilir. Ölçüm, korumasız hâlde bunun ne kadar kolay olduğunu gösterdi: 60 ardışık sorgu, 60 kez HTTP 200.Zamanlama tarafı da ölçüldü — ve orada sorun çıkmadı:
check_email, kayıtlı vs kayıtsız adres (150'şer dönüşümlü istek)
medyan farkı : 0,065 ms
p10 farkı : 0,024 ms
ölçüm gürültüsü: 0,431 ms
→ Zamanlamadan bilgi sızmıyor.Zaten sızmasına gerek yok: cevap düz metin olarak veriliyor. Bu, güvenlikte sık rastlanan bir yanılgının iyi bir örneğidir — yan kanal aramadan önce ön kapıya bakın.
Verilen karar ve gerekçesi
Canlı kontrol KALDI. Üstüne hız sınırı eklendi. Gerekçe:- Özelliği kaldırmak, projeyi kaldırmak olurdu. Bu depo tam olarak bu özelliği anlatıyor. Anlattığı şeyi silen bir “düzeltme” öğretici değildir.
- Sızdırılan bilgi tek bir bite indirildi. Uygulama, kayıtlı veriyi hiçbir yerde listelemez — ekranda gösterilen bir kayıt listesi, arama, sayaç ya da profil yoktur. Uç noktanın verebileceği tek bilgi, sorulan tek bir değer için “var / yok”tur. Toplu bir liste çekmenin yolu kapalıdır; kalan tek yol, değerleri tek tek denemektir — ve hız sınırı tam olarak bunu pahalılaştırır.
- Kısıtlama iki alana da uygulandı. Kullanıcı adı ile e-posta aynı kotayı paylaşır. Ayrı kota verseydik, sayım yapan biri iki kotayı da doldurup iki kat istek atardı.
- Hız sınırı sayımı imkânsız değil, PAHALI kılar. Bu dürüstçe söylenmelidir: dağıtık bir saldırgan (bot ağı) her istekte farklı IP kullanarak sınırı aşar. Sayımı tümüyle bitirmenin tek yolu özelliği kaldırmaktır.
Gerçek bir üründe ne yapardınız? Kayıt akışını “her zaman başarılı görünen” bir akışa çevirir, sonucu e-postayla bildirirsiniz (“bu adres zaten kayıtlıysa giriş bağlantısı gönderdik”). Böylece uç nokta hiçbir şey söylemez. Bunun bedeli, buradaki canlı geri bildirimin tümüyle kaybolmasıdır. Bu depo, bir demo olduğu için ödünleşimin UX tarafını seçti ve seçimini yazıya döktü — asıl öğretici olan da budur.
Hız sınırı nasıl ayarlandı?
| Uç nokta | Sınır | Neden bu sayı |
|---|---|---|
check_username / check_email | 40 / dakika (ortak kota) | Gerçek bir kullanıcı formu doldururken ~10-15 istek atar (500 ms geciktirme sayesinde). 16 istekle ölçüldü: sınır vurmuyor. |
submit | 5 / dakika | Bir insan dakikada 5 kez kayıt olmaz. Asıl amaç işlemciyi korumak — aşağıya bakın. |
submit sınırının gerçek gerekçesi ölçümdür: password_hash() bu makinede ~116 ms CPU harcıyor (bcrypt, cost 10) — bu, bir gönderimin toplam süresinin (~128 ms) yaklaşık %90'ı. Kimliği doğrulanmamış bir istekle 116 ms işlemci yaktırabilmek, ucuz bir hizmet dışı bırakma kaldıracıdır. Sınır, doğrulamadan önce uygulanır; sonraya bırakılsaydı geçersiz form gönderen bir bot sınıra hiç takılmadan sunucuyu meşgul ederdi.
Sayaç veritabanına değil, flock() ile kilitlenen bir dosyaya yazılır: her istekte bir INSERT atmak, korumaya çalıştığınız yükün ta kendisini üretirdi.
4. TOCTOU zinciri: üç katman, ölçülmüş
“Müsait” cevabı, kaydın başarılı olacağını garanti etmez:
Kullanıcı A: "ahmet" yazdı → canlı kontrol: müsait ✓
Kullanıcı B: "ahmet" yazdı → canlı kontrol: müsait ✓ (A henüz kaydetmedi)
Kullanıcı A: Kaydı gönderir → başarılı, "ahmet" alındı
Kullanıcı B: Kaydı gönderir → ???Bu klasik TOCTOU (time-of-check / time-of-use) açığıdır. Üç katmanla kapatılır:
| # | Katman | Nerede | Ne yakalar |
|---|---|---|---|
| 1 | Canlı kontrol | handle_check_username / handle_check_email | Hiçbir şey — yalnızca UX. Garanti vermez. |
| 2 | Son kontrol | handle_submit() içinde yeniden sorgu | Çakışmaların büyük çoğunluğu |
| 3 | UNIQUE indeks | Veritabanı | Gerçek eşzamanlılık — son söz |
Zinciri kırmayı denedim, kırılmadı
Aynı gönderim eşzamanlı iki istekle 12 tur denendi:| Senaryo | Sonuç | Oluşan kayıt |
|---|---|---|
| Aynı oturumdan iki istek | 200 + 422 (12/12) | her turda 1 |
| Farklı oturumlardan iki istek | 200 + 409 (12/12) | her turda 1 |
Mükerrer kayıt hiçbir turda oluşmadı.
Aradaki fark öğreticidir: aynı PHPSESSID ile gelen iki istek, PHP'nin oturum kilidi yüzünden sıraya girer — ikinci istek birincisi bittikten sonra çalışır ve 2. katman (son kontrol) onu yakalar. Gerçek yarış ancak farklı oturumlardaki iki kullanıcı arasında oluşur; orada 2. katman yetmez ve devreye 3. katman girer: UNIQUE indeks SQLSTATE 23000 fırlatır, uygulama bunu yakalayıp HTTP 409 ve anlaşılır bir mesajla döner. Ham SQL hatası kullanıcıya sızdırılmaz.
Yani 3. katman “ihtimale karşı” değildir — ölçümde 12/12 tetiklendi. İki katmanla yetinen bir kod, o 12 durumda mükerrer kayıt üretirdi.
5. Doğrulama kuralları — ve her birinin NEDENİ
| Alan | Kural | Zorunlu | Neden böyle |
|---|---|---|---|
| Ad Soyad | 2-100 karakter, \p{L}\p{M} + boşluk, ., ', - | ✔ |
\p{L} her dildeki harfi kapsar: “Ayşe”, “O'Brien”, “Jean-Luc” geçer. Rakam ve ` → 422 (\p{L}` deseni engelliyor). Kayıtlı veri zaten hiçbir yanıtta geri dönmediği için, saklanan (stored) XSS'in çıkabileceği bir yüzey de yok.
- Veri sızıntısı: Hiçbir uç nokta kayıt döndürmüyor. E-posta, telefon ve
password_hashyalnızca veritabanında; yanıtlarda arandı, bulunamadı. - Ölçek: 100.000 kayıtta
check_emailmedyanı ~6-7 ms;EXPLAINçıktısıtype=const, key=uniq_submissions_email, rows=1— indeks kullanılıyor.
7. API uç noktaları
Hepsisystem/ajax.php üzerinde, POST ile, CSRF token zorunlu.
action | Girdi | Başarı | Hata |
|---|---|---|---|
check_username | username | 200 {available, reason} | 403 429 |
check_email | email | 200 {available, reason} | 403 429 |
submit | Tüm form alanları | 200 {success, id} | 422 (alan hataları) · 409 (yarış) · 403 · 429 |
Bunlar tek uç noktalardır. Kayıtları okuyan bir uç yoktur: submissions tablosu yalnızca yazılır ve benzersizlik sorgularında karşılaştırma için okunur; hiçbir yanıtta kayıt listesi dönmez.
HTTP durum kodları ve anlamları
| Kod | Ne zaman | Neden bu kod |
|---|---|---|
200 | Başarılı — canlı kontroller dâhil | “Bu ad alınmış” bir cevaptır, hata değil. İstemci available alanına bakar. |
400 | Bilinmeyen veya boş action | İstek anlaşılmadı |
403 | CSRF token yok/geçersiz | İstek anlaşıldı, yetkilendirilmedi. 419 kullanmayın — bu Apache onu 500'e çeviriyor (ölçüldü) |
405 | POST dışı yöntem | Yöntem desteklenmiyor |
409 | Yarış durumu: UNIQUE indeks reddetti | Çakışma (conflict) — istek geçerliydi ama kaynağın durumu değişti |
422 | Alan doğrulama hataları | İstek biçimi doğru, içeriği işlenemez. Tüm hatalar errors{} içinde tek seferde döner |
429 | Hız sınırı aşıldı | Retry-After başlığı ve retry_after alanı ile |
Neden tüm hatalar tek seferde dönüyor? Kullanıcıyı “bir hatayı düzelt, sonrakini gör” döngüsüne sokmak, uzun bir formda kötü bir deneyimdir. Ölçüldü: yedi bozuk alanla gönderilen bir istek, yedi hatanın tamamını tek yanıtta döndürüyor.
8. Veritabanı şeması
CREATE TABLE `submissions` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`full_name` VARCHAR(100) NOT NULL,
`email` VARCHAR(190) NOT NULL, -- rules.php'deki 'max' ile AYNI sayı
`username` VARCHAR(20) NOT NULL,
`phone` VARCHAR(20) DEFAULT NULL,
`password_hash` VARCHAR(255) NOT NULL, -- password_hash() çıktısı
`birth_date` DATE DEFAULT NULL,
`message` VARCHAR(500) DEFAULT NULL,
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uniq_submissions_email` (`email`), -- TOCTOU zincirinin
UNIQUE KEY `uniq_submissions_username` (`username`) -- son halkası
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;Örnek veri: 60 kayıt — ve password_hash sütununda ne var?
Kurulum dosyası 60 örnek kayıtla gelir. Neden? Boş bir tabloyla kurulan proje kendi en önemli özelliğini gösteremez: canlı benzersizlik kontrolü denenemez — çakışacak kayıt olmadığı için her kullanıcı adı ve her e-posta “müsait” çıkar. Yani projeyi indiren kişi, formun yazarken cevap veren yanını hiç görmez. 60 kayıtla, kullanıcı adı alanına
ahmet yazıp kırmızı “zaten alınmış”, ahmet2 yazıp yeşil “✓ Müsait” cevabını ilk denemede görürsünüz.
Bu kayıtlar arayüzde listelenmez. Tablo hiçbir uçtan okunup ekrana basılmaz; yalnızca email_exists() / username_exists() sorgularının karşılaştırma yaptığı veri kümesidir.
password_hash sütununa ne kondu? 60 satırın hepsinde aynı, gerçek bir bcrypt çıktısı — OrnekParola123 parolasının hash'i. Üç sebeple sorun değildir:
- Bu bir giriş sistemi değildir. Hiçbir yerde
password_verify()çağrılmaz; bu sütun hiçbir kapıyı açmaz. Varlık sebebi tek bir prensibi göstermektir: parola düz metin saklanmaz. - Parola kamuya açıktır ve kasıtlıdır — bu satırda ve SQL dosyasının yorumunda yazılıdır. Sızabilecek bir sır yoktur.
- Düz metin yazmadık. Örnek veri bile olsa o sütunda düz metin görmek, kopyalayarak öğrenen birine yanlış deseni öğretirdi.
Gerçek bir sistemde bunu yapmayın: aynı hash'i çok kullanıcıya vermek, “bu iki kullanıcının parolası aynı” bilgisini sızdırır.password_hash()her çağrıda rastgele bir tuz (salt) üretir; aynı parola bile her seferinde farklı bir hash verir. Buradaki tekrarın tek sebebi SQL'inpassword_hash()çağıramamasıdır. Uygulamanın kendisi (system/ajax.php) her kayıttapassword_hash()çağırır — yani formdan giren gerçek kayıtlar tekil hash alır.
9. Kurulum
cd C:/xampp/htdocs
git clone https://github.com/CilginYazilim/form-validation-example.git
mysql -u root -p < form-validation-example/cy_validation.sql
Ya da phpMyAdmin → İçe Aktar → cy_validation.sql → Başlat.
Sonra: http://localhost/form-validation-example/
Gereken tek şey PHP 8.0+, MySQL 5.7+ ve bir Apache. Composer yok, npm yok, derleme adımı yok — jQuery ve Bootstrap depoda gömülü gelir.
Canlıya alırkensystem/config.phpiçindekiAPP_DEBUG'ıfalseyapın.
10. Dosya yapısı
form-validation-example/
├── index.php ← Form; kuralları JS'e aktarır
├── cy_validation.sql ← Veritabanı kurulumu + 60 örnek kayıt
├── .htaccess ← Dizin listeleme kapalı, .sql/.md kapalı, güvenlik başlıkları
├── system/
│ ├── .htaccess ← Beyaz liste: yalnızca ajax.php açık
│ ├── config.php ← Oturum güvenliği, PDO, hız sınırı ayarları
│ ├── rules.php ← ⭐ KURALLARIN TEK KAYNAĞI (PHP + JS buradan okur)
│ ├── function.php ← Doğrulayıcılar, CSRF, hız sınırı, veri erişimi
│ └── ajax.php ← check_username / check_email / submit
└── assets/
├── css/cilginyazilim.css ← Ortak marka tasarımı (dokunmayın)
├── css/style.css ← Sayfaya özel stiller
└── js/validation.js ← Canlı doğrulama, şifre ölçeri, sayaç
Hangi fonksiyon ne işe yarıyor?
| Fonksiyon | Dosya | İşi |
|---|---|---|
validation_rules() | rules.php | Tüm kural tanımlarını döndürür — tek kaynak |
rule_check() | rules.php | Ortak kısmı uygular: zorunluluk → uzunluk → desen |
rule_message() | rules.php | {min} / {max} / {age} yer tutucularını doldurur |
client_rules() | rules.php | Kuralların istemciye gidecek JSON hâli |
validate_*() | function.php | Alana özgü yordam (normalleştirme, tarih, URL) + rule_check() |
require_csrf() | function.php | Token doğrular, yoksa 403 |
rate_limit() | function.php | Kayan pencere sayacı, aşılırsa 429 + Retry-After |
email_exists() / username_exists() | function.php | Benzersizlik sorgusu (canlı kontrol + son kontrol aynı fonksiyonu kullanır) |
handle_submit() | ajax.php | Tüm alanları doğrular, hataları tek seferde toplar, kaydeder, 23000 → 409 |
ruleCheck() | validation.js | rule_check()'in birebir istemci karşılığı |
codePointLength() | validation.js | Kod noktası sayar — .length değil (astral ayrışmasının çözümü) |
passwordScore() | validation.js | Ölçer puanı; zorunlu kural sağlanmadıkça “Zayıf”ı geçmez |
11. Özelleştirme
Bir sınırı değiştirmek → yalnızcasystem/rules.php. PHP, JavaScript ve HTML maxlength birlikte hareket eder:
'full_name' => [ 'min' => 2, 'max' => 40, … ], // 100 yerine 40
'birth_date' => [ 'min_age' => 21, … ], // 18 yerine 21Yeni alan eklemek:
rules.php→ kural tanımıfunction.php→validate_yeni_alan()(yordamsal kısım varsa)ajax.php→handle_submit()içindeki listeye ekleindex.php→ `+data-error-for="yeni_alan"- validation.js
→validators.yeni_alan+fieldOrder` - SQL → sütun
Hız sınırını değiştirmek → system/config.php içindeki RATE_LIMIT_* sabitleri.
Vekil (proxy) arkasında çalıştırmak → client_fingerprint() fonksiyonu bilerek yalnızca REMOTE_ADDR okur; X-Forwarded-For okunmaz çünkü o başlık istemci tarafından uydurulabilir ve sınırı tek satırla kapatılabilir hâle getirir. Ters vekil arkasındaysanız orayı bilinçli olarak değiştirin.
12. Örnek kullanım alanları
- Üyelik / kayıt formları — projenin doğrudan konusu.
- İletişim ve talep formları — canlı benzersizlik kontrolünü çıkarıp kalan katmanları kullanın.
- Etkinlik / başvuru kayıtları — yaş sınırı, sözleşme onayı ve mükerrer başvuru engeli hazır gelir.
- Bülten aboneliği — e-posta benzersizliği + hız sınırı, tam olarak ihtiyaç duyulan iki şey.
- Yönetim paneli “kullanıcı ekle” ekranı — canlı kullanıcı adı kontrolü buraya birebir uyar.
- Eğitim materyali — “istemci doğrulaması neden güvenlik değildir” konusunu anlatmak için çalışan, ölçümleri yazılı bir örnek.
Lisans
MIT — dilediğiniz gibi indirip kullanabilirsiniz.
Çılgın Yazılım · github.com/CilginYazilim/form-validation-example
Telif © Çılgın Yazılım (cilginyazilim.com)
Kaynak Kod soldaki ağaçtan bir dosya seçin
-
assets
-
css
- bootstrap.min.css 227.5 KB
- cilginyazilim.css 19.4 KB
- style.css 2.4 KB
-
images
- logo.png 70.4 KB
- screenshot-live-validation.png 422.8 KB
-
js
- bootstrap.bundle.js 203.2 KB
- jquery-3.7.0.js 278.3 KB
- validation.js 21 KB
-
-
system
- .htaccess 1.8 KB
- ajax.php 9.6 KB
- config.local.php 151 B
- config.local.php.example 1.3 KB
- config.php 7.4 KB
- function.php 15.1 KB
- rules.php 11.8 KB
- .gitignore 1.1 KB
- .htaccess 3.7 KB
- cy_validation.sql 16.8 KB
- index.php 12.9 KB
- LICENSE 1.1 KB
- README.en.md 28.8 KB
- README.md 30 KB
Güvenlik gereği kaynak dosyalardaki parola, API anahtarı ve benzeri gizli
değerler gösterilmeden önce maskelenir (••••••••).
Sık Sorulan Sorular
Hayır. Tarayıcıdaki kod kullanıcının makinesinde, kullanıcının denetiminde çalışır: konsoldan doğrulama nesnesi silinebilir, betik hiç yüklenmeyebilir ya da form hiç açılmadan doğrudan uç noktaya istek atılabilir. Bu depoda tam olarak bu yapıldı; sunucu 422 döndürdü ve hiçbir kayıt oluşmadı. İstemci tarafı yalnızca kullanıcının hatayı görmek için sayfa yenilenmesini beklememesi içindir.
İki kez yazmak zorundasınız ama iki kez TANIMLAMAK zorunda değilsiniz. Bu örnekte sayısal sınırlar, desenler ve hata mesajları system/rules.php içinde bir kez veri olarak durur; PHP doğrudan okur, JavaScript aynı listeyi alır. Ayrı ayrı tanımlandığında ayrışma kaçınılmazdır: ölçümde 190 ve 72 karakterlik iki sınır istemcide hiç yazılmamıştı ve aynı girdiye iki farklı hata gerekçesi çıkıyordu.
Cevabın verildiği an ile kaydın yazıldığı an arasında başka biri aynı adı alabilir — buna kontrol ile kullanım arasındaki yarış koşulu (TOCTOU) denir. Canlı kontrol bir kolaylıktır, sınır değildir. Gerçek sınır veritabanındaki benzersiz indekstir; kayıt sırasında çakışma oluşursa hata yakalanıp kullanıcıya alan bazlı olarak bildirilir.
bcrypt algoritmasının sert sınırı budur: 72 baytın ötesindeki karakterler sessizce yok sayılır. Sınırı forma yazmazsanız kullanıcı 80 karakterlik bir şifre belirlediğini sanır, oysa yalnızca ilk 72 karakteri anlamlıdır. Bu yüzden sınır kural dosyasında açıkça tanımlanmış ve hata mesajıyla birlikte iki tarafa da aktarılmıştır.
JavaScript'in length özelliği UTF-16 kod birimi sayar; astral düzlemdeki bir karakter 2 sayılır. PHP tarafında mb_strlen kullanılırken istemcide length kullanılırsa aynı girdi iki farklı uzunluk verir. Ölçümde 60 harflik bir ad soyad istemcide 120 sayıldığı için geçerli girdi engelleniyordu; örnek bunu karakter bazlı sayımla çözüyor.
Yorumlar 0 konuşma
Bu kod örneğine henüz yorum yapılmamış. Takıldığınız bir yer veya merak ettiğiniz bir ayrıntı varsa ilk soruyu siz sorun.
Soru Sor veya Yorum Yaz
Yorumunuz onaylandıktan sonra yayınlanır. Teknik sorularınıza ekibimiz yanıt verir.