PHP PDO Ajax CRUD Örneği
DataTables, Bootstrap 5 ve modal pencerelerle çalışan, satır satır açıklamalı bir CRUD örneği. SQL enjeksiyonu, XSS ve dosya yükleme açıkları baştan kapatılmış.
Ekran Görüntüleri 4 görsel
Bu Örnek Ne Yapıyor?
- Tam CRUD: listeleme, ekleme, düzenleme, silme ve görsel yükleme
- Sunucu taraflı DataTables ile arama, sıralama ve sayfalama
- Bootstrap 5 modalları: tek modal hem ekleme hem düzenleme için kullanılıyor
- Sorgular hazırlanmış ifadelerle (prepared statement) çalışıyor — SQL enjeksiyonu kapalı
- Çıktı kaçışlama ile XSS, içerikten tür doğrulama ile dosya yükleme açığı kapatılmış
- Görseli olmayan kayıtlar için adın baş harfinden otomatik rozet üretiliyor
- Kodun her satırı Türkçe açıklamalı; Composer, npm ve internet bağlantısı gerekmiyor
Nerede İşe Yarar?
- PHP, Ajax ve DataTables üçlüsünü sıfırdan öğrenmek
- Yeni bir projeye hazır ve güvenli bir CRUD iskeletiyle başlamak
- Müşteri, personel veya ürün listesi gibi klasik yönetim ekranları
- Var olan bir CRUD kodunu güvenlik açısından karşılaştırmak
- Bootstrap 5 üzerine kurulu, tekrar kullanılabilir bir tasarım kalıbı edinmek
Gereksinimler
- PHP 8.0+ (pdo_mysql, gd) · MySQL 5.7+ veya MariaDB 10.3+ · Apache/mod_rewrite
Nasıl Kurulur?
- Proje dosyalarını web köküne kopyalayın (örn. XAMPP'ta htdocs veya kendi web sunucunuzun kök dizini)
- crud.sql dosyasını içe aktarın — tabloyu ve örnek kayıtları oluşturur
- system klasöründeki yapılandırma dosyasında veritabanı bilgilerini düzenleyin
- upload klasörüne yazma izni verin (kayıt görselleri buraya kaydedilir)
- Uygulamayı açın; listeye bir kayıt ekleyip modal üzerinden düzenlemeyi deneyin
Veritabanı şeması projedeki crud.sql dosyasında.
Nasıl Çalışıyor?
PHP PDO MySQL Ajax CRUD
DataTables · Bootstrap 5 · Modal · Çılgın Yazılım Tasarım Kalıbı
Güvenli, açıklamalı ve indirir indirmez çalışan bir CRUD örneği.
🇹🇷 Türkçe · 🇬🇧 English
▶ Canlı Demo · Kaynak Kütüphanesi · cilginyazilim.com
Canlı Demo
Kurulum yok, kayıt yok, indirme yok — tarayıcınızdan 3 saniyede deneyin.
▲ Görsele tıklayarak demoyu açabilirsinizDemoda 60 saniyede neleri deneyebilirsiniz?
| # | Şunu deneyin | Perde arkasında ne oluyor? |
|---|---|---|
| 1 | Arama kutusuna a yazın | Tarayıcı değil sunucu filtreliyor: LIKE sorgusu, % ve _ kaçışlanmış, recordsFiltered yeniden hesaplanıyor |
| 2 | "Ad" sütun başlığına tıklayın | Sıralama sütunu beyaz listeden geçiyor — istemciden gelen sütun adı sorguya asla girmiyor |
| 3 | 2. sayfaya geçin | LIMIT/OFFSET sunucuda; tarayıcıya sadece o sayfanın 10 satırı iniyor |
| 4 | 👁 Göz butonuna basın | action=fetch ham JSON dönüyor, HTML değil; ekran .text() ile dolduruluyor → XSS imkânsız |
| 5 | Yeni Kayıt → alanları boş bırakıp gönderin | Sunucu HTTP 422 + alan bazlı errors nesnesi dönüyor, mesaj ilgili inputun altına düşüyor |
| 6 | Ad alanına ` yazın | Kayıt reddediliyor; kaydedilse bile listeye e() ile kaçışlanmış çıkardı |
| 7 | Bir görsel seçin | Yüklemeden önce canlı önizleme; sunucuda tür getimagesize() ile dosya içeriğinden doğrulanıyor |
| 8 | .php uzantılı bir dosyayı .png yapıp yükleyin | Reddediliyor — uzantıya değil içeriğe bakılıyor, ayrıca upload/.htaccess` o klasörde PHP'yi kapatıyor |
| 9 | 🗑 Sil deyip onaylayın | Kayıt ve diskteki görsel birlikte siliniyor; dosya adı istemciden değil veritabanından okunuyor |
| 10 | İşletim sisteminizi koyu temaya alın | Arayüz otomatik koyu temaya geçiyor — tek satır JS yok, saf CSS |
İpucu: Demoyu açıkken F12 → Network sekmesini açın. Her istekteajax.php'ye gidenactionvecsrf_tokenalanlarını, dönen JSON'u ve HTTP durum kodlarını (200 / 419 / 422) canlı görebilirsiniz. Öğrenmenin en hızlı yolu budur.
Demo alanı hakkında bilinmesi gerekenler
| Konu | Durum |
|---|---|
| Veriler | crud.sql içindeki 50 örnek kayıt. Gerçek kişi verisi yoktur. |
| Sıfırlama | Demo veritabanı düzenli aralıklarla başlangıç haline döner; eklediğiniz kayıtlar kalıcı değildir. |
| Kimlik doğrulama | Yoktur. Bu bilinçli bir tercihtir — örnek, CRUD ve güvenlik katmanına odaklanır. Kendi projenizde mutlaka giriş sistemi ekleyin (bkz. Canlı Ortama Alırken). |
| Yükleme sınırı | Görsel başına 2 MB; yalnızca jpg, png, gif, webp. |
APP_DEBUG | Demoda false — canlı ortamda olması gerektiği gibi. Hata detayları ekranda değil log'da. |
| Bağımlılık | Sıfır. Composer yok, npm yok, CDN yok. Demo internetsiz bir sunucuda da aynı çalışır. |
Demo geçici olarak kapalıysa endişelenmeyin: depoyu klonlayıp crud.sql'i içe aktarmanız aynı ekranı kendi bilgisayarınızda 2 dakikada ayağa kaldırır → Kurulum
Bu Proje Nedir?
"PHP CRUD örneği" diye aratınca çıkan sonuçların çoğu aynı üç hataya sahiptir: sorgular $_POST ile birleştirilir, ekrana basılan veri kaçışlanmaz, dosya yükleme uzantıya güvenir. Yeni başlayan biri bu kodu kopyalayıp öğrenir — ve yanlışı da öğrenmiş olur.
Bu proje o kısır döngüyü kırmak için var: aynı CRUD'u, aynı sadelikte, ama gerçekten güvenli yazılmış haliyle gösteriyor. Fark satır sayısında değil, satırların neden öyle yazıldığında. getimagesize() neden explode('.', $name)'den daha güvenli, EMULATE_PREPARES = false ne değiştiriyor, hash_equals() olmadan CSRF token'ı neden kırılabilir — bunların hepsi kod içindeki yorumlarda, olduğu yerde anlatılıyor. Ayrı bir kitap okumanız gerekmiyor; dosyayı açıp okumanız yeterli.
Kimler için uygun?
- PHP + AJAX + DataTables üçlüsünü doğru öğrenmek isteyenler
- Kendi projesine hazır ve güvenli bir CRUD iskeleti arayanlar
- Bootstrap 5 üzerine kurulu, tekrar kullanılabilir bir tasarım kalıbı arayanlar
- Bir CRUD'un ne kadar mobil dostu, erişilebilir ve bağımlılıksız olabileceğini merak edenler
Klonla, crud.sql'i içe aktar, çalıştır. Başka hiçbir kurulum adımı yok. Composer yok, npm yok, internet bağlantısı bile gerekmiyor — tüm kütüphaneler proje içinde. Hiç kurulum yapmadan denemek isterseniz Canlı Demo bölümüne göz atın.
Bu proje, Çılgın Yazılım Kütüphanesi altında yayınlanan açıklamalı, üretime hazır örneklerden biridir — aynı tasarım kalıbıyla hazırlanmış diğer örnekleri de orada bulabilirsiniz.
İçindekiler
- Canlı Demo
- Ekran Görüntüleri
- Neler Var?
- Güvenlik: Neyi, Nasıl Kapattık?
- Kurulum
- Yapılandırma
- Çılgın Yazılım Tasarım Kalıbı
- Dosya Yapısı
- Nasıl Çalışıyor?
- AJAX API Referansı
- Veritabanı Şeması
- Sık Sorulanlar
- Canlı Ortama Alırken
- Sorun Giderme
- Yol Haritası
- Katkı
- Lisans
Ekran Görüntüleri
Kayıt listesi
Gradyanlı marka başlığı, canlı arama, sıralanabilir sütunlar ve tek sütunda toplanmış işlem butonları. Görseli olmayan kayıtlar için adın baş harfinden otomatik rozet üretilir.

Detay modalı
Göz butonuna basıldığında açılır. Büyük profil görseli, kayıt bilgileri ve düzenlemeye geçiş kısayolu içerir.

Ekleme / düzenleme formu
Tek bir modal hem ekleme hem düzenleme için kullanılır. Görsel seçildiğinde canlı önizleme gösterilir, hatalar ilgili alanın altında belirir.

Sunucu taraflı sayfalama
Sayfalama, arama ve sıralama sunucuda yapılır; tarayıcıya yalnızca görüntülenen sayfa gönderilir.

Arama
Ad ve soyad üzerinde arama yapılır. recordsFiltered doğru hesaplandığı için sayfalama filtreye uyum sağlar.

Üç modal:
| Modal | Açılış | İçerik |
|---|---|---|
| 👁 Detay | Göz butonu | Büyük profil görseli, kayıt no, ad, soyad, dosya adı, tarih + "Bu Kaydı Düzenle" kısayolu |
| ✎ Ekle / Düzenle | Yeni Kayıt veya kalem butonu | Form, canlı görsel önizleme, alan bazlı hata mesajları |
| 🗑 Sil | Çöp kutusu butonu | Kimin silineceğini yazan onay ekranı |
Neler Var?
Arayüz
- Marka gradyanlı başlık ve modallar
- Üç ayrı modal (detay / form / silme onayı)
- Sağ üstte toast bildirimleri
- Görsel canlı önizleme (yüklemeden önce)
- Görselsiz kayıtlar için baş harf rozeti
- Otomatik koyu tema (işletim sistemi ayarını izler)
- Mobil için ayrıca inceltilmiş — dokunma hedefleri ≥40px, tablo yatay kaydırmaya zorlamaz, erişilebilir (ARIA etiketli)
- Tamamı Türkçe — CDN'siz, çevrimdışı çalışır
Altyapı
- Sunucu taraflı (server-side) DataTables
- Tek AJAX uç noktası,
actiontabanlı yönlendirme - Çift katmanlı doğrulama (istemci + sunucu)
- Alan bazlı hata mesajları (HTTP 422)
- Otomatik dosya temizliği (yetim dosya bırakmaz)
- Ortam değişkeni desteği
- 50 hazır örnek kayıt
- Kodun her satırı açıklamalı
Güvenlik: Neyi, Nasıl Kapattık?
İnternetteki benzer örneklerin çoğunda bulunan açıklar ve bu projede nasıl önlendikleri:
| Açık | Tipik hatalı kod | Bu projede |
|---|---|---|
| SQL Injection | "SELECT FROM users WHERE id = '".$_POST['id']."'" | Tüm sorgular prepared statement. EMULATE_PREPARES = false ile gerçek prepared statement zorunlu. Sıralama sütunu/yönü beyaz listeden geçer — order[0][column]=0;DROP TABLE users-- denemesi test edildi, etkisiz. |
| XSS | $sub_array[] = $row["name"]; | Veritabanından gelen her değer e() (htmlspecialchars) ile kaçışlanır. Toast mesajları .text() ile yazılır, .html() ile değil. |
| CSRF | (genelde hiç yok) | Oturuma bağlı 32 baytlık token. ` etiketinden okunup her AJAX isteğine eklenir, hash_equals() ile sabit zamanlı doğrulanır. Token'sız istek → HTTP 419. |
| Kötü amaçlı dosya yükleme | $ext = explode('.', $name)[1]; | Uzantıya güvenilmez: tür getimagesize() ile dosya içeriğinden tespit edilir. Yeni ad random_bytes() ile üretilir, uzantı MIME beyaz listesinden atanır. shell.php.png yükleme denemesi test edildi, reddedildi. |
| Yükleme klasöründe kod çalıştırma | (korumasız klasör)* | upload/.htaccess PHP motorunu kapatır ve çalıştırılabilir uzantılara erişimi engeller. |
| Path Traversal | unlink("../upload/".$_POST['hidden_image']) | Silinecek dosya adı istemciden değil veritabanından okunur, ayrıca basename() ile temizlenir. |
| Tip karmaşası | WHERE id = '$id' | ID'ler filter_input(..., FILTER_VALIDATE_INT) ile doğrulanır. |
| Bilgi sızıntısı | Ekrana basılan MySQL hataları | APP_DEBUG = false iken hata detayı gizlenir, error_log()'a yazılır. |
| LIKE joker karakterleri | LIKE '%$search%' | Arama terimindeki %, _, \ kaçışlanır — kullanıcı % yazınca tüm tablo dönmez. |
| Kaynak tüketimi | LIMIT $length | Sayfa boyutu 500 ile sınırlanır; length=999999` sunucuyu yormaz. |
Kurulum
Sadece görmek istiyorsanız kurulum gerekmez → Canlı Demoyu açın. Aşağıdaki adımlar, projeyi kendi bilgisayarınızda çalıştırmak içindir (~2 dakika).
Gereksinimler
- PHP 8.0+ (
pdo_mysqlvegdeklentileri) - MySQL 5.7+ veya MariaDB 10.3+
- Apache (XAMPP / WAMP / Laragon) — ya da PHP'nin yerleşik sunucusu
Adımlar
1 — Projeyi indirin
git clone https://github.com/CilginYazilim/php-pdo-ajax-crud.git
cd php-pdo-ajax-crud2 — Veritabanını oluşturun
crud.sql veritabanını da kendisi oluşturur; önceden crud adında bir veritabanı açmanıza gerek yok.
mysql -u root -p < crud.sql
İsteğe bağlı — kendi veritabanı bilgileriniz:
cp .env.example .env(Windows:copy .env.example .env) deyipDB_*
satırlarını doldurun. Bu dosya olmadan da çalışır; varsayılanlar yerel bir
XAMPP kurulumuna (root, boş parola) göredir..env.gitignore
içindedir — parolanız depoya gitmez.
phpMyAdmin ile: İçe Aktar → Dosya seç → crud.sql → Başlat
3 — Çalıştırın
php -S 127.0.0.1:8000XAMPP kullanıyorsanız projeyi
htdocs altına koymanız ve şu adresi açmanız yeterli:http://localhost/php-pdo-ajax-crud/
4 — Tarayıcıda açın → http://127.0.0.1:8000/
Karşınıza 50 örnek kayıt dolu, çalışır durumda bir tablo gelecek.
Linux/macOS kullanıcıları:upload/klasörüne yazma izni gerekir →chmod 755 upload
Ortam değişkenleri
Depo kökündeki .env dosyasına yazın; system/config.php dosyasına
hiç dokunmayın:
cp .env.example .env # Windows: copy .env.example .env.env .gitignore içindedir: depoya gönderilmez ve dağıtım (deploy) onusilmez.
system/config.php ise depoda durur ve her dağıtımda depodakisürümle değiştirilir — parolayı oraya yazarsanız hem GitHub'a gider hem de
ilk deploy'da kaybolur.
Dosyayı hiç oluşturmasanız da uygulama çalışır; aşağıdaki varsayılanlar
yerel bir XAMPP kurulumuna göredir.
Değer arama sırası: .env → sunucunun gerçek ortam değişkeni
(Apache SetEnv, systemd…) → buradaki varsayılan.
| Değişken | Varsayılan | Ne işe yarar |
|---|---|---|
DB_HOST | 127.0.0.1 | Veritabanı sunucusu |
DB_NAME | crud | Veritabanı adı |
DB_USER | root | Kullanıcı |
DB_PASS | (boş) | Şifre — koda yazmayın |
APP_TIMEZONE | Europe/Istanbul | PHP'nin saat dilimi |
APP_DEBUG | ortamdan | Hataların ekrana basılıp basılmayacağı |
APP_TIMEZONE neden var? XAMPP'ın php.ini dosyasındaki
date.timezone, MySQL'in kullandığı sistem diliminden farklı olabilir.
Test makinesinde PHP Europe/Berlin, MySQL Europe/Istanbul
kullanıyordu; aynı anı anlatan iki satır bir saat farklı görünüyordu.
Zaman hesapları SQL tarafında yapıldığı için doğruydu, ama ekrana
basılan saat kayıyordu. Artık dilim açıkça sabitleniyor — sunucunuz başka
bir bölgedeyse bu değişkeni tanımlamanız yeterli, koda dokunmayın.
Yapılandırma
Tüm ayarlar system/config.php içinde, açıklamalarıyla birlikte:
| Sabit | Varsayılan | Açıklama |
|---|---|---|
DB_HOST | 127.0.0.1 | Veritabanı sunucusu |
DB_NAME | crud | Veritabanı adı |
DB_USER | root | Kullanıcı adı |
DB_PASS | (boş) | Parola |
UPLOAD_MAX_BYTES | 2 MB | Maksimum görsel boyutu |
ALLOWED_IMAGE_TYPES | jpg, png, gif, webp | İzin verilen MIME türleri |
NAME_MIN_LENGTH / NAME_MAX_LENGTH | 2 / 150 | Ad-soyad uzunluk sınırları |
APP_DEBUG | true | Canlıda false yapın |
Şifreyi koda yazmayın
Tüm DB_* sabitleri ortam değişkeniyle geçersiz kılınabilir. Böylece şifreniz GitHub'a düşmez:
# Linux / macOS
export DB_HOST=localhost DB_USER=uygulama DB_PASS='••••••••'
# Windows (PowerShell)
$env:DB_USER = "uygulama"; $env:DB_PASS = "••••••••"Apache için
.htaccess ya da httpd.conf içinde: SetEnv DB_PASS "guclu-sifre"
Çılgın Yazılım Tasarım Kalıbı
assets/css/cilginyazilim.css dosyası, bu projeye değil markaya aittir. Diğer örnek projelerde de aynı görsel dili kullanabilmek için ayrı bir dosya olarak tutulur.
Başka bir projede kullanmak
<!-- 1) Bootstrap'ten SONRA ekleyin -->
<link rel="stylesheet" href="assets/css/bootstrap.min.css">
<link rel="stylesheet" href="assets/css/cilginyazilim.css">
<!-- 2) body'ye tema sınıfını verin -->
<body class="cy-app">
Hazır bileşenler
| Sınıf | Ne işe yarar | ||
|---|---|---|---|
.cy-card / .cy-card__header / .cy-card__body / .cy-card__footer | Gradyan başlıklı ana kart | ||
.cy-brand / .cy-brand__mark | Logo kutusu + başlık bloğu | ||
.cy-btn + .cy-btn--primary \ | --onbrand | Marka butonları | |
.cy-btn-icon + --view \ | --edit \ | --delete | Tablo içi ikon butonları |
.cy-table | Marka görünümlü tablo | ||
.cy-avatar / .cy-avatar--initial / .cy-avatar--lg | Profil görseli ve baş harf rozeti | ||
.cy-badge + --glass \ | --soft | Rozetler | |
.cy-modal | Gradyan başlıklı modal | ||
.cy-detail | Etiket/değer listesi (detay modalı) | ||
.cy-toast + --success \ | --danger \ | --info | Bildirim balonları |
Renkleri değiştirmek
Tüm bileşenler CSS değişkenlerinden beslenir. Tek yeri değiştirmek yeter:
:root {
--cy-brand-900: #061321; /* Logodaki en koyu lacivert */
--cy-brand-600: #0b5cb5; /* Ana marka mavisi */
--cy-accent: #0ea5e9; /* Vurgu rengi */
--cy-gradient: linear-gradient(135deg, #061321, #0b5cb5 45%, #0284c7);
}
Renk paleti cilginyazilim.com logosundan türetilmiştir: koyu lacivertten canlı maviye uzanan geçiş. Logo assets/images/logo.png içinde yer alır ve başlıkta beyaz yuvarlak bir zemine oturtulur.
Koyu tema
Kullanıcının işletim sistemi koyu temadaysa otomatik devreye girer. Zorlamak isterseniz:
<html data-cy-theme="dark"> <!-- veya "light" -->
Dosya Yapısı
.
├── index.php # Arayüz + tüm JavaScript mantığı
├── .env.example # Veritabanı bilgileri (isteğe bağlı) — .gitignore içinde
├── crud.sql # Veritabanı şeması + 50 örnek kayıt
├── README.md # Türkçe belgelendirme
├── README.en.md # İngilizce belgelendirme
├── LICENSE # MIT lisansı
├── .gitignore
│
├── docs/
│ └── screenshots/ # README'de kullanılan ekran görüntüleri
│
├── system/
│ ├── config.php # Ayarlar, oturum, PDO bağlantısı
│ ├── function.php # Yardımcı fonksiyonlar
│ └── ajax.php # AJAX uç noktası / CRUD yönlendiricisi
│
├── assets/
│ ├── css/
│ │ ├── bootstrap.min.css
│ │ ├── dataTables.bootstrap5.min.css
│ │ ├── cilginyazilim.css # ★ MARKA TASARIM KALIBI
│ │ └── style.css # Sadece bu sayfaya özel eklemeler
│ └── js/
│ ├── jquery-3.7.0.js
│ ├── bootstrap.bundle.js
│ ├── jquery.dataTables.min.js
│ └── dataTables.bootstrap5.min.js
│
└── upload/
├── .htaccess # Klasörde kod çalıştırmayı engeller
└── *.png # Örnek görsellerYükleme sırası önemlidir:
CSS: bootstrap → dataTables → cilginyazilim → style
JS: jQuery → bootstrap.bundle → dataTables → dataTables.bootstrap5Sıra bozulursa
$ is not defined gibi hatalar alırsınız.
Nasıl Çalışıyor?
┌─────────────────────────────────────────────────────────────────────┐
│ TARAYICI (index.php) │
│ │
│ DataTables ──┐ │
│ Form gönder ─┤ │
│ Detay butonu ┼──► jQuery AJAX ──► POST { action, csrf_token, ... } │
│ Sil butonu ──┘ │ │
└──────────────────────────────────────────────┼───────────────────────┘
▼
┌─────────────────────────────────────────────────────────────────────┐
│ SUNUCU (system/ajax.php) │
│ │
│ 1. POST mu? → değilse 405 │
│ 2. require_csrf() → geçersizse 419 │
│ 3. action'a göre dağıt: list │ add │ edit │ fetch │ delete │
│ 4. validate_name() → hatalıysa 422 + errors │
│ 5. upload_image() → içerikten tür doğrulama │
│ 6. PDO prepared query → MySQL │
│ 7. json_response() → tek noktadan JSON çıkışı │
│ │
│ Tüm bunlar try/catch içinde: hiçbir hata çıplak PHP mesajı olarak │
│ sızmaz, hepsi düzgün JSON'a çevrilir. │
└─────────────────────────────────────────────────────────────────────┘
Sorumluluk dağılımı
| Dosya | Görevi |
|---|---|
| index.php | Sadece sunum. CSRF token üretir, DataTables'ı kurar, modalları ve toast'ları yönetir. Veritabanına dokunmaz. |
| system/ajax.php | Yönlendirici. action değerine göre işleyicilere dağıtır. Tüm güvenlik kontrolleri ve hata yakalama burada tek noktada. |
| system/function.php | Saf yardımcı fonksiyonlar. Veritabanına ihtiyaç duyanlar PDO'yu parametre olarak alır — her çağrıda yeni bağlantı açılmaz. |
| system/config.php | Oturum, sabitler ve tek bir PDO örneği. |
Öğrenirken dikkat çeken noktalar
Kod içindeki yorumlarda ayrıntısıyla anlatılan, yeni başlayanların sık takıldığı konular:
serverSide: truene demek? — 50 kayıtta fark etmez ama 100.000 kayıtta tarayıcıya tüm veriyi göndermemenizi sağlar.- Event delegation — AJAX ile sonradan gelen butonlara neden
$('.js-edit').click()çalışmaz,$('#user_data').on('click', '.js-edit', ...)neden çalışır. EMULATE_PREPARES = falsetuzağı — aynı isimli yer tutucu (:search) neden iki kez kullanılamaz,Invalid parameter numberhatası nereden gelir.contentType: false, processData: false— dosya yüklerken bu ikisi neden zorunlu.- Sütun adı neden bind edilemez — ve bu yüzden neden beyaz liste şart.
AJAX API Referansı
Tüm istekler POST ile system/ajax.php adresine yapılır ve geçerli bir csrf_token içermelidir. Yanıtlar application/json türündedir.
action=list — DataTables listeleme
İstek: draw, start, length, search[value], order[0][column], order[0][dir]
Yanıt:
{
"draw": 1,
"recordsTotal": 50,
"recordsFiltered": 50,
"data": [[50, "<img …>", "Ozan", "TOPAL", "<span…>23.02.2025 23:28</span>", "<div class=\"cy-actions\">…</div>"]]
}Sıralanabilir sütunlar beyaz listeyle sınırlıdır:
0 → id, 2 → name, 3 → surname, 4 → tarih.Foto (1) ve İşlemler (5) sütunları veritabanı sütunu olmadıkları için sıralanamaz.
action=add — Yeni kayıt
İstek: name, surname, image_user (opsiyonel, multipart/form-data)
Başarılı (200):
{ "success": true, "type": "success", "description": "Kayıt başarıyla eklendi.", "id": 51 }Doğrulama hatası (422):
{
"success": false,
"type": "danger",
"description": "Lütfen formdaki hataları düzeltin.",
"errors": { "name": "Ad alanı boş bırakılamaz." }
}errors nesnesinin anahtarları form alanlarının id'leriyle birebir aynıdır; JavaScript mesajı doğrudan ilgili alanın altına yazar.
action=edit — Güncelleme
İstek: user_id, name, surname, image_user (opsiyonel)
Yeni görsel gönderilmezse mevcut görsel korunur. Gönderilirse yenisi kaydedilir ve eski dosya diskten silinir.
action=fetch — Tek kayıt (detay + düzenleme)
İstek: id
{
"success": true,
"id": 1,
"name": "Evren",
"surname": "ÇILGIN",
"image": "2090273627.png",
"image_url": "upload/2090273627.png",
"tarih": "06.01.2025 19:34"
}Hazır HTML değil ham veri döner; ekranı JavaScript
.text() ile doldurduğu için XSS riski oluşmaz.
action=delete — Silme
İstek: id
Kayıt ve ilişkili görsel dosyası birlikte silinir. Kayıt yoksa 404 döner.
HTTP durum kodları
| Kod | Anlamı |
|---|---|
200 | İşlem başarılı |
400 | Geçersiz parametre (örn. hatalı ID) |
404 | Kayıt bulunamadı |
405 | POST dışı istek |
419 | CSRF token geçersiz veya oturum düşmüş |
422 | Form doğrulama hatası (errors alanı döner) |
500 | Sunucu / veritabanı hatası |
Veritabanı Şeması
CREATE TABLE `users` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(150) NOT NULL,
`surname` VARCHAR(150) NOT NULL,
`image` VARCHAR(191) NOT NULL DEFAULT '', -- sadece dosya adı, tam yol değil
`tarih` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
KEY `idx_users_name` (`name`), -- arama ve sıralama için
KEY `idx_users_surname` (`surname`),
KEY `idx_users_tarih` (`tarih`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
| Karar | Neden |
|---|---|
| InnoDB (MyISAM değil) | Transaction ve foreign key desteği, satır bazlı kilitleme |
| utf8mb4 (utf8mb3 değil) | Türkçe karakterler + emoji; eski utf8 bazı karakterleri saklayamaz |
| İndeksler | Arama/sıralama yapılan sütunlar; tablo büyüdükçe fark katlanarak açılır |
| Sadece dosya adı | Klasör yapısı değişirse veritabanına dokunmak gerekmez |
Mevcut (eski) bir kurulumu veri kaybetmeden yükseltmek için crud.sql dosyasının sonundaki ALTER TABLE komutlarını kullanın.
Sık Sorulanlar
Bu kodu kendi projemde kullanabilir miyim?
Evet. MIT lisanslı — ticari projeler dahil serbestçe kullanabilir, değiştirebilir, dağıtabilirsiniz. Atıf zorunlu değil ama sevindirir.
Tabloya yeni bir sütun eklemek istiyorum, nereleri değiştirmeliyim?
Örnek: email sütunu eklemek için beş nokta:
crud.sql→ tabloya sütunu ekleyinindex.php→ forma input, `'eekleyin- system/function.php
→find_user()içindekiSELECTlistesine ekleyin - system/ajax.php
→handle_save()içinde doğrulama +INSERT/UPDATE,handle_list()içinde$data[]dizisine yeni hücre - system/ajax.php
→ sütun eklediyseniz$sortableColumns` indekslerini kaydırmayı unutmayın
` sayısı ile $data[]` dizisinin uzunluğu aynı olmak zorundadır, aksi halde DataTables hata verir.
Görsel boyutu sınırını nasıl artırırım?
system/config.php içinde UPLOAD_MAX_BYTES değerini değiştirin. Ayrıca php.ini içindeki upload_max_filesize ve post_max_size değerlerini de artırın — PHP'nin limiti sizinkinden düşükse dosya sunucuya hiç ulaşmaz.
Neden hem istemcide hem sunucuda doğrulama var?
İstemci doğrulaması kullanıcı deneyimi içindir: kullanıcı sunucuya gitmeden anında geri bildirim alır. Ama JavaScript kapatılabilir veya istek doğrudan curl ile atılabilir. Bu yüzden gerçek koruma her zaman sunucudadır. İkisi birlikte kullanılır.
DataTables'ın serverSide: true ayarını kapatabilir miyim?
Kapatabilirsiniz ama o zaman tüm kayıtlar tek seferde tarayıcıya gönderilir. 50 kayıtta sorun olmaz; 50.000 kayıtta sayfa donar. Bu örnekte gerçek dünya senaryosunu göstermek için açık bırakıldı.
Bootstrap 5 sürümünü güncelleyebilir miyim?
Evet, assets/ altındaki dosyaları değiştirmeniz yeterli. Tasarım kalıbı Bootstrap'in üzerine ekleme yapar, onu değiştirmez — bu yüzden sürüm yükseltmeleri sorunsuz geçer.
Canlı Ortama Alırken
- [ ]
APP_DEBUGdeğerinifalseyapın - [ ] Veritabanı için
rootyerine sınırlı yetkili bir kullanıcı oluşturun - [ ] Kimlik bilgilerini ortam değişkeni olarak tanımlayın, koda gömmeyin
- [ ] HTTPS kullanın;
session.cookie_secure = 1vesession.cookie_httponly = 1ayarlayın - [ ] Nginx kullanıyorsanız
.htaccessçalışmaz — yükleme klasöründe PHP'yi sunucu yapılandırmasından kapatın:
```nginx
location ^~ /upload/ {
location ~ \.php$ { deny all; }
}
```
- [ ]
upload/klasörünü düzenli yedekleyin - [ ] Giriş sistemi ekleyin — bu örnekte kimlik doğrulama yoktur, herkes tüm kayıtları düzenleyebilir
Sorun Giderme
| Belirti | Çözüm |
|---|---|
| "Veritabanına bağlanılamadı" | MySQL çalışmıyor veya DB_* bilgileri hatalı. XAMPP panelinden MySQL'i başlatın. |
| Tablo boş, "Yükleniyor…" takılı | Tarayıcı konsolunu (F12) açın. Genelde system/ajax.php bir PHP hatası döndürüyordur; APP_DEBUG = true yapıp Network sekmesinden yanıtı okuyun. |
| HTTP 419 hatası | Oturum düşmüş — sayfayı yenileyin. Sunucuda session.save_path yazılabilir olmalıdır. |
Invalid parameter number | Aynı isimli yer tutucuyu bir sorguda iki kez kullanmışsınız. EMULATE_PREPARES = false iken buna izin verilmez; farklı isimler verin. |
$ is not defined | JavaScript yükleme sırası bozulmuş. jQuery her zaman en başta gelmelidir. |
| "Görsel kaydedilemedi" | upload/ klasörü yok veya yazma izni yok → chmod 755 upload |
| Türkçe karakterler bozuk | Veritabanı utf8mb4 değil. crud.sql sonundaki CONVERT TO CHARACTER SET utf8mb4 komutunu çalıştırın. |
| Büyük dosya yüklenmiyor | php.ini içindeki upload_max_filesize ve post_max_size değerlerini artırın. |
| DataTables "Requested unknown parameter" | `` sayısı ile sunucudan dönen dizi uzunluğu farklı. İkisini eşitleyin. |
Yol Haritası
- [ ] Kullanıcı girişi ve rol tabanlı yetkilendirme
- [ ] Toplu silme (checkbox ile çoklu seçim)
- [ ] Excel / CSV / PDF dışa aktarma (DataTables Buttons)
- [ ] Sunucu tarafında görsel yeniden boyutlandırma ve thumbnail
- [ ] REST API katmanı (JWT ile)
- [ ] Soft delete + işlem geçmişi (audit log)
- [ ] PHPUnit ile birim testleri
- [ ] Koyu tema için elle açma/kapama düğmesi
Katkı
Bu proje herkese açıktır — dilediğiniz geliştirmeyle katkı sağlayabilirsiniz.
📦 Depo: github.com/CilginYazilim/php-pdo-ajax-crud
| Nasıl katkı sağlarım? | Nereden |
|---|---|
| 🐛 Hata bildir | Issues |
| 💡 Özellik öner | Issues |
| 🔧 Kod gönder | Pull Requests |
| ❓ Soru sor | Discussions |
Pull request adımları
# 1) Depoyu çatallayın (GitHub'da "Fork" butonu), sonra kendi kopyanızı indirin
git clone https://github.com/KULLANICI-ADINIZ/php-pdo-ajax-crud.git
cd php-pdo-ajax-crud
# 2) Değişikliğiniz için yeni bir dal açın
git checkout -b ozellik/yeni-ozellik
# 3) Kodunuzu yazın, sonra kaydedin
git add .
git commit -m "Yeni özellik: kısa ve açıklayıcı bir başlık"
# 4) Kendi çatalınıza gönderin
git push origin ozellik/yeni-ozellik
# 5) GitHub'da "Compare & pull request" butonuna tıklayın
Katkı ölçütleri
- Kod açıklamalı olsun. Bu projenin temel amacı öğretmek; yorumsuz kod PR'ı geri döner.
- Güvenlik kontrollerini atlamayın. Prepared statement,
e()ile kaçışlama verequire_csrf()her yeni işlemde de olmalı. - Tasarım değişikliklerini
cilginyazilim.cssüzerinden yapın, satır içistyle="..."kullanmayın. - Sütun eklerken `
sayısı ile$data[]` uzunluğunu eşitlemeyi unutmayın. - Yeni bir dış kütüphane eklemeden önce issue açıp tartışalım — proje bilinçli olarak bağımlılıksızdır.
Lisans
MIT — ticari kullanım dahil serbesttir.
Önce bir deneyin
cilginyazilim.com tarafından ❤ ile geliştirildi
Faydalı bulduysanız ⭐ vermeyi unutmayın.
Kaynak Kod soldaki ağaçtan bir dosya seçin
-
assets
-
css
- bootstrap.css 232.4 KB
- bootstrap.min.css 227.5 KB
- cilginyazilim.css 22.3 KB
- dataTables.bootstrap5.min.css 11.7 KB
- style.css 2 KB
-
images
- logo.png 70.4 KB
-
js
- bootstrap.bundle.js 203.2 KB
- bootstrap.js 142.3 KB
- dataTables.bootstrap5.min.js 2.3 KB
- jquery-3.7.0.js 278.3 KB
- jquery.dataTables.min.js 85.1 KB
-
-
docs
-
screenshots
- 01-liste.png 172.4 KB
- 02-detay-modali.png 175.4 KB
- 03-form-modali.png 168.7 KB
- 04-sayfalama.png 201.5 KB
- 05-arama.png 148.8 KB
- README.md 1008 B
-
-
system
- ajax.php 19.9 KB
- config.php 11 KB
- function.php 16.7 KB
-
upload
- .htaccess 357 B
- 1130289535.png 3.1 KB
- 1131702286.png 4.1 KB
- 1832783644.png 70.4 KB
- 2090273627.png 3.6 KB
- e6287bb48cd300c39ebd39d535f5e74b.png 70.4 KB
- .gitignore 1.1 KB
- .htaccess 2.7 KB
- CHANGELOG.md 3.8 KB
- crud.sql 7 KB
- index.php 37.2 KB
- LICENSE 1.1 KB
- README.en.md 34.9 KB
- README.md 35.4 KB
Güvenlik gereği kaynak dosyalardaki parola, API anahtarı ve benzeri gizli
değerler gösterilmeden önce maskelenir (••••••••).
Sık Sorulan Sorular
Kullanıcıdan gelen hiçbir değer sorgu metnine yazılmaz. Tüm sorgular PDO'nun hazırlanmış ifadeleriyle çalışır: sorgunun yapısı önce veritabanına gönderilir, değerler ayrı parametre olarak bağlanır. Böylece bir değerin içindeki tırnak ya da SQL anahtar kelimesi sorgunun anlamını değiştiremez.
Veritabanından gelen her metin ekrana basılmadan önce kaçışlanır (htmlspecialchars). Böylece bir kaydın içine yazılmış HTML ya da betik, sayfada çalışacak kod olarak değil düz metin olarak görünür. Kaçışlama girişte değil çıkışta yapılır; veriyi bozmadan doğru katmanda çözmenin yolu budur.
Hayır. Klonlayın, crud.sql dosyasını içe aktarın, çalıştırın. Başka hiçbir kurulum adımı yoktur; Composer, npm ve hatta internet bağlantısı bile gerekmez, çünkü tüm kütüphaneler proje içinde durur. Paylaşımlı hostinglerde de doğrudan çalışır.
Yüklenen dosyanın türü, tarayıcının gönderdiği başlığa değil dosyanın gerçek içeriğine bakılarak doğrulanır ve yalnızca izin verilen görsel türleri kabul edilir. Dosya adı yeniden üretilir, böylece kullanıcının verdiği ad diske yazılan adı belirlemez. Daha derin bir savunma isterseniz kütüphanedeki güvenli dosya yükleme örneği on iki katmanlı sürümü anlatır.
Evet, MIT lisanslıdır ve tam olarak bunun için hazırlanmıştır. Tablo adını ve alanları kendi verinize göre değiştirip aynı kalıbı sürdürebilirsiniz: sunucu taraflı liste, modal form, Ajax uçları ve doğrulama akışı aynen kalır. Canlıya alırken hata gösterimini kapatmayı unutmayın.
Yorumlar 1 konuşma
Soru Sor veya Yorum Yaz
Yorumunuz onaylandıktan sonra yayınlanır. Teknik sorularınıza ekibimiz yanıt verir.