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ış.

PHP 8 PDO MySQL Ajax DataTables Bootstrap 5
Seviye
Başlangıç
Dosya
34
Kod satırı
~27.829
Proje boyutu
2.4 MB
Veritabanı
crud
Lisans
MIT
İnceleme
94
Beğeni
2
Yayın

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?

  1. Proje dosyalarını web köküne kopyalayın (örn. XAMPP'ta htdocs veya kendi web sunucunuzun kök dizini)
  2. crud.sql dosyasını içe aktarın — tabloyu ve örnek kayıtları oluşturur
  3. system klasöründeki yapılandırma dosyasında veritabanı bilgilerini düzenleyin
  4. upload klasörüne yazma izni verin (kayıt görselleri buraya kaydedilir)
  5. 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.

PHP
MySQL
Bootstrap
DataTables
License

🇹🇷 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.

Canlı Demoyu Aç
 
Kaynak Kodu İncele
 
ZIP İndir

PHP PDO Ajax CRUD canlı demo önizlemesi

▲ Görsele tıklayarak demoyu açabilirsiniz

Demoda 60 saniyede neleri deneyebilirsiniz?

#Şunu deneyinPerde arkasında ne oluyor?
1Arama kutusuna a yazınTarayıcı değil sunucu filtreliyor: LIKE sorgusu, % ve _ kaçışlanmış, recordsFiltered yeniden hesaplanıyor
2"Ad" sütun başlığına tıklayınSıralama sütunu beyaz listeden geçiyor — istemciden gelen sütun adı sorguya asla girmiyor
32. sayfaya geçinLIMIT/OFFSET sunucuda; tarayıcıya sadece o sayfanın 10 satırı iniyor
4👁 Göz butonuna basınaction=fetch ham JSON dönüyor, HTML değil; ekran .text() ile dolduruluyor → XSS imkânsız
5Yeni Kayıt → alanları boş bırakıp gönderinSunucu HTTP 422 + alan bazlı errors nesnesi dönüyor, mesaj ilgili inputun altına düşüyor
6Ad alanına ` yazınKayıt reddediliyor; kaydedilse bile listeye e() ile kaçışlanmış çıkardı
7Bir görsel seçinYü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ükleyinReddediliyor — 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ınKayıt ve diskteki görsel birlikte siliniyor; dosya adı istemciden değil veritabanından okunuyor
10İşletim sisteminizi koyu temaya alınArayüz otomatik koyu temaya geçiyor — tek satır JS yok, saf CSS
İpucu: Demoyu açıkken F12 → Network sekmesini açın. Her istekte ajax.php'ye giden action ve csrf_token alanları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

KonuDurum
Verilercrud.sql içindeki 50 örnek kayıt. Gerçek kişi verisi yoktur.
SıfırlamaDemo veritabanı düzenli aralıklarla başlangıç haline döner; eklediğiniz kayıtlar kalıcı değildir.
Kimlik doğrulamaYoktur. 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_DEBUGDemoda false — canlı ortamda olması gerektiği gibi. Hata detayları ekranda değil log'da.
BağımlılıkSı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


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.

Kayıt listesi

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.

Detay modalı

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.

Form modalı

Sunucu taraflı sayfalama

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

Sayfalama

Arama

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

Arama

Üç modal:

ModalAçılışİçerik
👁 DetayGöz butonuBüyük profil görseli, kayıt no, ad, soyad, dosya adı, tarih + "Bu Kaydı Düzenle" kısayolu
Ekle / DüzenleYeni Kayıt veya kalem butonuForm, canlı görsel önizleme, alan bazlı hata mesajları
🗑 SilÇöp kutusu butonuKimin 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ı, action tabanlı 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çıkTipik hatalı kodBu 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 Traversalunlink("../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 karakterleriLIKE '%$search%'Arama terimindeki %, _, \ kaçışlanır — kullanıcı % yazınca tüm tablo dönmez.
Kaynak tüketimiLIMIT $lengthSayfa 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_mysql ve gd eklentileri)
  • 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-crud

2 — 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) deyip DB_*
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:8000

XAMPP 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çınhttp://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) onu
silmez. system/config.php ise depoda durur ve her dağıtımda depodaki
sü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şkenVarsayılanNe işe yarar
DB_HOST127.0.0.1Veritabanı sunucusu
DB_NAMEcrudVeritabanı adı
DB_USERrootKullanıcı
DB_PASS(boş)Şifre — koda yazmayın
APP_TIMEZONEEurope/IstanbulPHP'nin saat dilimi
APP_DEBUGortamdanHataları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:

SabitVarsayılanAçıklama
DB_HOST127.0.0.1Veritabanı sunucusu
DB_NAMEcrudVeritabanı adı
DB_USERrootKullanıcı adı
DB_PASS(boş)Parola
UPLOAD_MAX_BYTES2 MBMaksimum görsel boyutu
ALLOWED_IMAGE_TYPESjpg, png, gif, webpİzin verilen MIME türleri
NAME_MIN_LENGTH / NAME_MAX_LENGTH2 / 150Ad-soyad uzunluk sınırları
APP_DEBUGtrueCanlı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ıfNe işe yarar
.cy-card / .cy-card__header / .cy-card__body / .cy-card__footerGradyan başlıklı ana kart
.cy-brand / .cy-brand__markLogo kutusu + başlık bloğu
.cy-btn + .cy-btn--primary \--onbrandMarka butonları
.cy-btn-icon + --view \--edit \--deleteTablo içi ikon butonları
.cy-tableMarka görünümlü tablo
.cy-avatar / .cy-avatar--initial / .cy-avatar--lgProfil görseli ve baş harf rozeti
.cy-badge + --glass \--softRozetler
.cy-modalGradyan başlıklı modal
.cy-detailEtiket/değer listesi (detay modalı)
.cy-toast + --success \--danger \--infoBildirim 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örseller

Yükleme sırası önemlidir:

CSS:  bootstrap → dataTables → cilginyazilim → style
JS:   jQuery → bootstrap.bundle → dataTables → dataTables.bootstrap5

Sı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ı

DosyaGörevi
index.phpSadece sunum. CSRF token üretir, DataTables'ı kurar, modalları ve toast'ları yönetir. Veritabanına dokunmaz.
system/ajax.phpYönlendirici. action değerine göre işleyicilere dağıtır. Tüm güvenlik kontrolleri ve hata yakalama burada tek noktada.
system/function.phpSaf yardımcı fonksiyonlar. Veritabanına ihtiyaç duyanlar PDO'yu parametre olarak alır — her çağrıda yeni bağlantı açılmaz.
system/config.phpOturum, 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: true ne 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 = false tuzağı — aynı isimli yer tutucu (:search) neden iki kez kullanılamaz, Invalid parameter number hatası 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ı

KodAnlamı
200İşlem başarılı
400Geçersiz parametre (örn. hatalı ID)
404Kayıt bulunamadı
405POST dışı istek
419CSRF token geçersiz veya oturum düşmüş
422Form doğrulama hatası (errors alanı döner)
500Sunucu / 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;
KararNeden
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
İndekslerArama/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:

  1. crud.sql → tabloya sütunu ekleyin
  2. index.php → forma input, `'e ekleyin
  3. system/function.phpfind_user() içindeki SELECT listesine ekleyin
  4. system/ajax.phphandle_save() içinde doğrulama + INSERT/UPDATE, handle_list() içinde $data[] dizisine yeni hücre
  5. 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_DEBUG değerini false yapın
  • [ ] Veritabanı için root yerine 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 = 1 ve session.cookie_httponly = 1 ayarlayı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 numberAynı isimli yer tutucuyu bir sorguda iki kez kullanmışsınız. EMULATE_PREPARES = false iken buna izin verilmez; farklı isimler verin.
$ is not definedJavaScript 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 bozukVeritabanı utf8mb4 değil. crud.sql sonundaki CONVERT TO CHARACTER SET utf8mb4 komutunu çalıştırın.
Büyük dosya yüklenmiyorphp.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 bildirIssues
💡 Özellik önerIssues
🔧 Kod gönderPull Requests
❓ Soru sorDiscussions

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 ve require_csrf() her yeni işlemde de olmalı.
  • Tasarım değişikliklerini cilginyazilim.css üzerinden yapın, satır içi style="..." 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

Canlı Demoyu Aç
&nbsp;
Diğer Örnekler

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
Dosya seçilmedi
İncelemek istediğiniz dosyayı soldaki ağaçtan seçin.

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

A
Aytek
DataTables'ı ilk defa kullanıyorum, burada tam olarak ne işe yarıyor?

Soru Sor veya Yorum Yaz

Yorumunuz onaylandıktan sonra yayınlanır. Teknik sorularınıza ekibimiz yanıt verir.

Yayınlanmaz; yalnızca yanıt bildirimi için kullanılır.
En az 10 karakter.

İlgili Kod Örnekleri