
PHP'de PDO ile Veritabanı İşlemleri: Bağlantı, Sorgu ve İşlem Yönetimi
PDO, farklı veritabanları için ortak bir arayüz sunar. Sürücü değiştiğinde sorgu metinleri farklılaşsa da bağlantı, hazırlama ve okuma kodu aynı kalır. Bu bölümde bağlantının doğru yapılandırılması, hazır sorguların kullanımı, okuma kipleri ve toplu işlem yönetimi ele alınıyor.
Yapılandırma seçenekleri çoğu projede varsayılan haliyle bırakılır ve bu, sonradan fark edilen sorunların önemli bir bölümünün kaynağıdır. Hatalar sessizce yutulur, sayılar metin olarak döner, karakter kümesi ayarlanmadığı için Türkçe karakterler bozulur.
Bağlantı kurmak
Bağlantı bir kez kurulur ve uygulama boyunca paylaşılır. Bağlantı dizesinde karakter kümesinin belirtilmesi zorunludur; belirtilmediğinde sunucu varsayılanı kullanılır ve Türkçe metinlerde bozulma yaşanır.
$dsn = 'mysql:host=127.0.0.1;dbname=uygulama;charset=utf8mb4';
$pdo = new PDO($dsn, $kullanici, $parola, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // hatalar istisnaya dönüşür
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, // yalnızca ad ile erişim
PDO::ATTR_EMULATE_PREPARES => false, // gerçek hazır sorgu kullanılır
PDO::ATTR_STRINGIFY_FETCHES => false, // sayılar sayı olarak döner
]);Öykünmüş hazır sorgular kapatıldığında, sorgu metni ile parametreler veritabanı sunucusuna ayrı ayrı gönderilir. Bu, enjeksiyona karşı en güçlü yapısal korumadır ve tip bilgisinin korunmasını da sağlar. Bağlantı bilgileri koda gömülmez; ortam değişkeninden okunur.
Bağlantının gerçekten ihtiyaç duyulduğunda kurulması, sayfa üretimi sırasında veritabanına hiç gidilmeyen isteklerde gereksiz maliyeti ortadan kaldırır. Bunun için bağlantı bir sınıf içinde tembel biçimde oluşturulur.
final class Baglanti
{
private ?PDO $pdo = null;
public function __construct(
private readonly string $dsn,
private readonly string $kullanici,
private readonly string $parola,
) {}
public function al(): PDO
{
return $this->pdo ??= new PDO($this->dsn, $this->kullanici, $this->parola, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]);
}
}Hazır sorgular
Kullanıcı verisi sorgu metnine birleştirilmez, parametre olarak bağlanır. İsimli parametreler okunabilirliği artırır ve parametre sırası değiştiğinde hata riskini ortadan kaldırır.
$sorgu = $pdo->prepare(
'SELECT id, baslik, olusturma FROM yazilar
WHERE kategori_id = :kategori AND durum = :durum
ORDER BY olusturma DESC
LIMIT :adet'
);
$sorgu->bindValue(':kategori', $kategoriId, PDO::PARAM_INT);
$sorgu->bindValue(':durum', 'yayinda', PDO::PARAM_STR);
$sorgu->bindValue(':adet', $adet, PDO::PARAM_INT); // LIMIT için tip belirtilmeli
$sorgu->execute();
$satirlar = $sorgu->fetchAll();Parametre dizisi doğrudan çalıştırma metoduna da verilebilir. Bu kısa yolda tüm değerler metin olarak bağlanır; sayısal sınır ifadelerinde tip belirtmek gerektiği için orada ayrı bağlama tercih edilir.
$sorgu = $pdo->prepare('SELECT * FROM kullanicilar WHERE eposta = ?');
$sorgu->execute([$eposta]);
$kullanici = $sorgu->fetch(); // tek satır ya da false
$sayi = $pdo->query('SELECT COUNT(*) FROM kullanicilar')->fetchColumn();Tablo ve sütun adları parametre olarak bağlanamaz. Bu değerler kullanıcıdan geliyorsa, izin verilen adların bulunduğu bir listeyle eşleştirilerek doğrulanmalıdır.
$izinli = ['olusturma', 'baslik', 'okunma'];
$sutun = in_array($_GET['sirala'] ?? '', $izinli, strict: true)
? $_GET['sirala']
: 'olusturma';
$sorgu = $pdo->prepare("SELECT * FROM yazilar ORDER BY {$sutun} DESC");Liste parametreleri
Bir küme içinde arama yapan sorgularda parametre sayısı çalışma zamanında belirlenir. Yer tutucular dizinin boyutuna göre üretilir; değerler yine bağlanarak gönderilir.
$idler = [4, 9, 17, 23];
$yerTutucu = implode(',', array_fill(0, count($idler), '?'));
$sorgu = $pdo->prepare("SELECT id, baslik FROM yazilar WHERE id IN ({$yerTutucu})");
$sorgu->execute($idler);Aynı yaklaşım toplu ekleme sorgularında da kullanılır: değer grupları dizinin boyutuna göre üretilir ve tüm değerler tek bir çalıştırmada bağlanır. Kayıt başına ayrı sorgu göndermeye kıyasla ağ gidiş dönüş sayısını belirgin biçimde azaltır.
Dizinin boş olabileceği durumlar ayrıca ele alınmalıdır; boş bir küme ile kurulan sorgu sözdizimi hatası verir. Bu tür sorgularda önce eleman sayısı denetlenir.
Yazma işlemleri
$sorgu = $pdo->prepare(
'INSERT INTO yazilar (baslik, icerik, olusturma)
VALUES (:baslik, :icerik, :olusturma)'
);
$sorgu->execute([
'baslik' => $baslik,
'icerik' => $icerik,
'olusturma' => (new DateTimeImmutable('now', new DateTimeZone('UTC')))->format('Y-m-d H:i:s'),
]);
$yeniId = (int) $pdo->lastInsertId();
// güncellemede etkilenen satır sayısı
$guncelle = $pdo->prepare('UPDATE yazilar SET durum = ? WHERE id = ?');
$guncelle->execute(['yayinda', $yeniId]);
$etkilenen = $guncelle->rowCount();Etkilenen satır sayısı her zaman güvenilir bir başarı ölçütü değildir. Aynı değerin yeniden yazıldığı bir güncellemede bazı sürücüler sıfır döndürür. Başarı ölçütü, istisna fırlatılmamış olmasıdır.
Toplu işlemler
Birden fazla yazma işlemi mantıksal olarak tek bir bütün oluşturuyorsa, hepsinin ya birlikte tamamlanması ya da hiçbirinin uygulanmaması gerekir. İşlem bloğu bu garantiyi sağlar.
$pdo->beginTransaction();
try {
$stokDus->execute(['adet' => $adet, 'id' => $urunId]);
$siparisEkle->execute(['kullanici' => $kullaniciId, 'tutar' => $tutar]);
$pdo->commit();
} catch (Throwable $e) {
$pdo->rollBack();
throw $e; // hata yutulmaz, yukarı taşınır
}İşlem blokları iç içe açılmamalıdır; çoğu sürücü bunu desteklemez ve içteki tamamlama, dıştaki bloğu da kapatır. İşlem yönetimi, iş akışını bilen tek bir katmanda toplanır. Uzun süren işlemler de kilit tuttukları için kısa tutulmalıdır; dış servis çağrıları işlem bloğunun içine alınmaz.
Okuma kipleri
Varsayılan kip ile satırlar birleşimli dizi olarak döner. Farklı ihtiyaçlar için ayrı kipler bulunur; doğru kipin seçilmesi hem kodu kısaltır hem de gereksiz dönüşümleri ortadan kaldırır.
// tek sütunluk liste
$idler = $pdo->query('SELECT id FROM yazilar')->fetchAll(PDO::FETCH_COLUMN);
// anahtar => değer eşlemesi
$sozluk = $pdo->query('SELECT id, baslik FROM yazilar')->fetchAll(PDO::FETCH_KEY_PAIR);
// doğrudan sınıf örneklerine eşleme
$sorgu = $pdo->prepare('SELECT id, baslik FROM yazilar WHERE durum = ?');
$sorgu->setFetchMode(PDO::FETCH_CLASS, Yazi::class);
$sorgu->execute(['yayinda']);Büyük sonuç kümelerinde tüm satırların tek seferde belleğe alınması bellek sınırını zorlar. Sorgu nesnesi üzerinde doğrudan dolaşıldığında satırlar teker teker alınır ve bellek kullanımı sabit kalır.
$sorgu = $pdo->query('SELECT id, baslik FROM yazilar');
foreach ($sorgu as $satir) { // tüm sonuç belleğe alınmaz
isle($satir);
}Tekrarlayan sorgular ve N+1
Bir liste çekip her satır için ayrı sorgu çalıştırmak, sayfa üretim süresini kayıt sayısıyla doğru orantılı büyütür. Yirmi kayıtlık bir listede bu, tek sorgu yerine yirmi bir sorgu anlamına gelir.
// sorunlu: her yazı için ayrı sorgu
foreach ($yazilar as $yazi) {
$yazar = $pdo->prepare('SELECT ad FROM yazarlar WHERE id = ?');
$yazar->execute([$yazi['yazar_id']]);
}
// çözüm: tek sorguda birleştirmek
$sorgu = $pdo->prepare(
'SELECT y.id, y.baslik, z.ad AS yazar
FROM yazilar y
INNER JOIN yazarlar z ON z.id = y.yazar_id
WHERE y.durum = ?'
);
$sorgu->execute(['yayinda']);Aynı sorgunun döngü içinde tekrar tekrar hazırlanması da gereksiz maliyettir. Hazırlama bir kez yapılır, çalıştırma her kayıt için tekrarlanır. Toplu yazma işlemlerinde bu düzen, tek bir işlem bloğuyla birleştirildiğinde belirgin hızlanma sağlar.
$ekle = $pdo->prepare('INSERT INTO kayitlar (ad, deger) VALUES (?, ?)');
$pdo->beginTransaction();
foreach ($satirlar as $satir) {
$ekle->execute([$satir['ad'], $satir['deger']]); // hazırlama tekrarlanmaz
}
$pdo->commit();Birleştirmenin uygun olmadığı durumlarda ikinci sorgu tek seferde çalıştırılır: önce tüm kimlikler toplanır, ardından küme sorgusuyla ilgili kayıtlar çekilip bellekte eşleştirilir. İki sorgu, kayıt sayısı ne olursa olsun sabit kalır.
Sorgu sayısının ve süresinin ölçülmesi, sorunu tahmin etmeden görmeyi sağlar. Geliştirme ortamında istek başına çalışan sorguların sayılması, listeye yeni bir alan eklendiğinde sorgu sayısının sessizce artmasını fark ettirir. Yavaş sorguların veritabanı tarafında kaydedilmesi de aynı amaca hizmet eder.
Kontrol listesi
- Bağlantı dizesinde
charset=utf8mb4tanımlı. - Hata kipi istisna, öykünmüş hazır sorgular kapalı.
- Kullanıcı verisi sorguya birleştirilmiyor, parametre olarak bağlanıyor.
- Tablo ve sütun adları izin listesiyle doğrulanıyor.
- Bağlantı bilgileri ortam değişkeninden okunuyor.
- İlişkili yazma işlemleri tek bir işlem bloğunda; hata durumunda geri alınıyor.
- Liste sayfalarında satır başına ek sorgu çalışmıyor.
İlgili yazılar
- SQL injection ve hazır sorgularla savunma
- Veritabanı indeksleme stratejileri
- İstisna yönetimi ve geri alma düzeni
PHP eğitim serisi
Bu yazı, temel sözdiziminden üretim ortamına kadar ilerleyen 25 bölümlük serinin bir parçası. Bölümler sırayla okunacak biçimde hazırlandı, ancak her biri tek başına da kullanılabilir.
Temeller
- Kurulum ve temel sözdizimi
- Döngüler ve akış kontrolü
- Diziler
- Fonksiyonlar
- String fonksiyonları
- Süper globaller
- Form işleme
- Dosya işlemleri
Dilin ayrıntıları
Nesne yönelimli PHP
- Sınıf ve nesne
- Kalıtım, arayüz ve trait
- Statik üyeler, sabitler ve enum
- Namespace, Composer ve autoload
Veri ve güvenlik
- PDO ile veritabanı işlemleri (bu yazı)
- Oturum yönetimi ve kimlik doğrulama
- Güvenli çıktı ve XSS
- JSON ile veri değişimi
Dış dünya ve üretim
Yorumlar
İlk yorumu sen yap.
İlgili Yazılar

PHP Uygulamasını Üretime Almak: Yapılandırma ve Güvenlik
Geliştirme makinesinde çalışan kod ile üretimde güvenle çalışan kod arasındaki fark yapılandırmadır. Ayarlar, izinler, dağıtım akışı ve son kontroller.

PHP ile REST API Yazmak: Yönlendirme ve HTTP Durum Kodları
Bir API'nin kalitesi çoğunlukla ayrıntılarda belli olur: doğru durum kodu, tutarlı hata biçimi ve öngörülebilir adresler. Saf PHP ile uçtan uca kurulum.

PHPUnit ile Birim Test Yazmak: Kurulum, Sahte Nesneler ve Kapsam
Testin amacı hata bulmak değil, değişikliği güvenle yapabilmektir. Kurulum, ilk test, sahte nesneler ve test edilebilir kodun özellikleri.