aslain.dev
0%
01 Hizmetler 02 Hakkımda 03 Projeler 04 Stack 05 Blog 06 İletişim
← Tüm makaleler Web Geliştirme

PHP PDO ile Güvenli Veritabanı Sorguları

PHP PDO, modern PHP uygulamalarında veritabanına bağlanmanın en güvenli ve esnek yoludur. Doğru kullanıldığında, web uygulamalarına yönelik en yaygın ve en tehlikeli saldırılardan biri olan SQL injection'ı neredeyse tamamen ortadan kaldırır. Bu yazıda PDO ile bağlantı kurmayı, prepared statement (hazırlanmış sorgu) mantığını ve günlük geliştirmede işine yarayacak pratik kalıpları adım adım göreceksin.

SQL injection neden bu kadar tehlikeli?

SQL injection, kullanıcıdan gelen verinin doğrudan SQL sorgusuna yapıştırılmasıyla oluşur. Saldırgan, beklenen bir değer yerine sorgunun anlamını değiştiren bir metin gönderir. Klasik ve hatalı örnek şudur:

// ASLA BÖYLE YAPMA
$email = $_POST['email'];
$sql = "SELECT * FROM users WHERE email = '$email'";
$result = $pdo->query($sql);

Eğer kullanıcı email alanına ' OR '1'='1 yazarsa, sorgu her zaman doğru döner ve tüm kullanıcıların kaydı sızdırılabilir. Daha kötüsü, saldırgan tabloları silebilir veya yetki yükseltebilir. Çözüm basittir: veriyi hiçbir zaman doğrudan SQL metnine karıştırma.

PDO bağlantısını doğru kurmak

İyi bir PDO bağlantısı, hataların sessizce yutulmasını engelleyen ve davranışı öngörülebilir kılan birkaç ayar içerir. Bağlantıyı her zaman bir try/catch bloğuna al ve kimlik bilgilerini koda gömmek yerine ortam değişkenlerinden oku.

$dsn = 'mysql:host=127.0.0.1;dbname=app;charset=utf8mb4';

$options = [
    PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
    PDO::ATTR_EMULATE_PREPARES   => false,
];

try {
    $pdo = new PDO($dsn, $user, $pass, $options);
} catch (PDOException $e) {
    // Hatayı logla, kullanıcıya ham mesajı gösterme
    error_log($e->getMessage());
    exit('Veritabanına bağlanılamadı.');
}

Üç ayar kritik: ERRMODE_EXCEPTION hataları istisna olarak fırlatır (sessiz hata yerine), FETCH_ASSOC sonuçları okunabilir ilişkisel dizi olarak verir ve EMULATE_PREPARES = false sürücünün gerçek prepared statement kullanmasını sağlar; bu da güvenlik ve tip doğruluğu açısından önemlidir.

Prepared statement mantığı

Prepared statement, sorgunun yapısını ve verisini birbirinden ayırır. Önce sorgu şablonunu yer tutucularla hazırlarsın, sonra değerleri ayrı olarak gönderirsin. Böylece veri, ne kadar tehlikeli görünürse görünsün, asla SQL komutu olarak yorumlanmaz; her zaman bir değer olarak ele alınır.

$stmt = $pdo->prepare('SELECT id, name FROM users WHERE email = ?');
$stmt->execute([$email]);
$user = $stmt->fetch();

Burada ? bir yer tutucudur. execute() içine geçirdiğin dizi, yer tutucuların yerine güvenli biçimde yerleştirilir. Sürücü, değeri verinin parçası olarak işler ve injection imkânı ortadan kalkar.

İsimli (named) parametreler

Sorgu birden çok parametre içeriyorsa, sıraya bağlı ? yerine isimli yer tutucular kodu daha okunur kılar. İsimli parametreler iki nokta üst üste ile başlar:

$stmt = $pdo->prepare(
    'INSERT INTO posts (title, body, user_id) VALUES (:title, :body, :user_id)'
);

$stmt->execute([
    ':title'   => $title,
    ':body'    => $body,
    ':user_id' => $userId,
]);

Daha fazla kontrol istiyorsan bindValue() ile her parametrenin tipini de belirleyebilirsin:

$stmt = $pdo->prepare('SELECT * FROM products WHERE stock >= :min');
$stmt->bindValue(':min', $min, PDO::PARAM_INT);
$stmt->execute();

Sonuçları okumak

PDO, sonuçları okumak için birkaç yöntem sunar. Tek satır için fetch(), tüm satırlar için fetchAll() kullanırsın. Çok sayıda satırı işlerken bellek dostu yol, sonucu döngüyle tek tek çekmektir:

$stmt = $pdo->prepare('SELECT id, title FROM posts WHERE user_id = ?');
$stmt->execute([$userId]);

foreach ($stmt as $row) {
    echo htmlspecialchars($row['title']) . "\n";
}

Dikkat: Veritabanından gelen veriyi ekrana basarken htmlspecialchars() kullan. PDO seni SQL injection'dan korur, ancak XSS (çapraz site betik çalıştırma) ayrı bir konudur ve çıktı tarafında önlem ister.

Sık yapılan hatalar

  • Tablo veya sütun adını parametre sanmak. Yer tutucular yalnızca değerler içindir; ORDER BY ? çalışmaz. Sütun/tablo adlarını sabit bir beyaz liste ile doğrula.
  • EMULATE_PREPARES'i açık bırakmak. Açık kaldığında PDO sorguyu kendi tarafında birleştirir; çoğu durumda güvenli olsa da gerçek prepared statement her zaman daha sağlamdır.
  • Hataları kullanıcıya göstermek. Ham hata mesajları veritabanı yapısını ifşa eder. Logla, kullanıcıya genel bir mesaj ver.
  • Aynı sorguyu döngüde yeniden hazırlamak. Bir kez prepare() et, döngüde sadece execute() çağır.

İşlemler (transactions)

Birbirine bağlı birden fazla yazma işlemi yapıyorsan, hepsinin ya hep ya hiç çalışmasını sağlamak için transaction kullan. Bir adım başarısız olursa rollBack() ile her şeyi geri alırsın:

try {
    $pdo->beginTransaction();

    $pdo->prepare('UPDATE accounts SET balance = balance - ? WHERE id = ?')
        ->execute([$amount, $fromId]);

    $pdo->prepare('UPDATE accounts SET balance = balance + ? WHERE id = ?')
        ->execute([$amount, $toId]);

    $pdo->commit();
} catch (PDOException $e) {
    $pdo->rollBack();
    throw $e;
}

Sık Sorulan Sorular

PDO mu yoksa MySQLi mi kullanmalıyım?

İkisi de prepared statement destekler ve güvenlidir. PDO'nun avantajı, on iki farklı veritabanı sürücüsünü tek bir arayüzle desteklemesi ve isimli parametreler gibi daha okunur bir API sunmasıdır. Çoğu yeni proje için PDO daha esnek bir tercihtir.

Prepared statement performansı düşürür mü?

Hayır, genelde tam tersi. Aynı sorgu farklı değerlerle tekrar çalıştırıldığında veritabanı sorgu planını yeniden kullanabilir. Üstelik tek seferlik sorgularda fark ihmal edilebilir düzeydedir; kazandığın güvenlik bu maliyetten kat kat değerlidir.

Sadece prepared statement kullanmak yeterli mi?

SQL injection'a karşı evet, doğru kullanıldığında yeterlidir. Ancak güvenlik bütüncüldür: çıktıda XSS koruması, giriş doğrulama, yetkilendirme ve en az ayrıcalık ilkesi de eklenmelidir.

Veritabanı güvenliğini ciddiye al. Eğer mevcut bir PHP projeni gözden geçirmemi veya güvenli bir veri katmanı kurmamı istiyorsan, benimle iletişime geç ve birlikte sağlam bir temel atalım.

Bu kategorideki tüm yazılar →

Devamı için