İçeriğe geç

MySQL ile PHP: Veritabanı Bağlantısı ve Temel CRUD İşlemleri

Modern web uygulamalarının bel kemiği, dinamik içerik sunma yetenekleridir. Bu yetenek, genellikle PHP gibi bir sunucu tarafı betik dili ile MySQL gibi bir ilişkisel veritabanı yönetim sisteminin entegrasyonuyla sağlanır. PHP, web tarayıcılarından gelen istekleri işlerken, MySQL verileri düzenli bir şekilde depolar, yönetir ve erişilebilir kılar. Bu başlangıç rehberi, PHP ve MySQL arasındaki bu kritik bağlantıyı kurmanın temel prensiplerini adım adım açıklayacaktır. Veritabanı bağlantısı kurmaktan, veri sorgulama ve manipülasyon işlemlerine, özellikle de prepared statement kullanımının önemiyle güvenlik odaklı yaklaşımlara kadar birçok konuyu ele alacağız. Amacımız, size sağlam ve güvenli bir web uygulaması altyapısı oluşturmanız için gerekli temel bilgileri sunmaktır.

Veritabanı bağlantısı kurma: mysqli ve pdo

PHP ile bir MySQL veritabanına bağlanmak, her dinamik web uygulamasının ilk ve en temel adımıdır. Bu bağlantı sayesinde PHP, veritabanına sorgular gönderebilir, veri alabilir veya veri üzerinde değişiklik yapabilir. PHP, MySQL ile etkileşim kurmak için başlıca iki uzantı sunar: mysqli (MySQL Improved Extension) ve PDO (PHP Data Objects).

mysqli uzantısı

mysqli uzantısı, sadece MySQL veritabanlarıyla çalışmak üzere tasarlanmıştır ve hem prosedürel hem de nesne yönelimli bir arayüz sunar. Genellikle daha eski veya MySQL'e özgü projelerde tercih edilebilir.


<?php
$servername = "localhost";
$username = "kullanici_adi";
$password = "sifre";
$dbname = "veritabani_adi";

// Nesne yönelimli bağlantı
$conn_oo = new mysqli($servername, $username, $password, $dbname);

// Bağlantı kontrolü
if ($conn_oo->connect_error) {
    die("Bağlantı hatası: " . $conn_oo->connect_error);
}
echo "<p>mysqli ile başarıyla bağlandı (OO)!</p>";
$conn_oo->close();

// Prosedürel bağlantı
$conn_proc = mysqli_connect($servername, $username, $password, $dbname);

// Bağlantı kontrolü
if (!$conn_proc) {
    die("Bağlantı hatası: " . mysqli_connect_error());
}
echo "<p>mysqli ile başarıyla bağlandı (Prosedürel)!</p>";
mysqli_close($conn_proc);
?>

pdo (php data objects) uzantısı

PDO ise farklı veritabanı türleriyle (MySQL, PostgreSQL, SQLite vb.) tek tip bir arayüz üzerinden çalışmayı sağlayan, veritabanından bağımsız bir soyutlama katmanıdır. Bu özelliği sayesinde, projenizin gelecekte farklı bir veritabanına geçme ihtimali varsa PDO daha esnek bir yapı sunar. Güvenlik ve gelişmiş özellikler açısından da genellikle daha tercih edilir.

Bu rehberde, PDO'nun sunduğu avantajlar ve daha geniş kullanım alanı nedeniyle tüm örneklerimizi PDO üzerinden ilerleteceğiz. Bağlantı kurarken istisna yönetimi (try-catch blokları) kullanarak olası hataları daha zarif bir şekilde yakalamak en iyi pratiktir.


<?php
$servername = "localhost";
$username = "kullanici_adi";
$password = "sifre";
$dbname = "veritabani_adi";

try {
    $conn = new PDO("mysql:host=$servername;dbname=$dbname;charset=utf8", $username, $password);
    // Hata modunu istisna olarak ayarla
    $conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    // Varsayılan fetch modunu ayarla (örn: dizi olarak döndürme)
    $conn->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
    echo "<p>PDO ile başarıyla bağlandı!</p>";
} catch(PDOException $e) {
    die("Bağlantı hatası: " . $e->getMessage());
}
// Bağlantı kapatma genellikle gerekli değildir; script sona erdiğinde otomatik kapanır.
// Ancak açıkça null atayarak da kapatabilirsiniz: $conn = null;
?>

Yukarıdaki PDO bağlantı kodunda, ATTR_ERRMODE ayarını ERRMODE_EXCEPTION olarak belirterek, veritabanı işlemlerinde oluşacak hataların PDOException nesneleri olarak fırlatılmasını sağlıyoruz. Bu sayede hataları try-catch bloklarıyla kolayca yönetebiliriz. ATTR_DEFAULT_FETCH_MODE ayarı ise sorgu sonuçlarının varsayılan olarak nasıl alınacağını belirler; FETCH_ASSOC ile sonuçlar ilişkilendirilebilir bir dizi olarak döner.

Veri sorgulama ve manipülasyonu: select, insert, update, delete

Veritabanı bağlantısı kurulduktan sonra, veritabanındaki verilerle etkileşime geçmeye başlayabiliriz. Bu etkileşim, temelde dört ana işlemden oluşur: veri seçme (SELECT), veri ekleme (INSERT), veri güncelleme (UPDATE) ve veri silme (DELETE). Bu işlemlere genellikle CRUD (Create, Read, Update, Delete) operasyonları denir.

Bu bölümde, PDO kullanarak bu temel sorguların nasıl çalıştırılacağını göstereceğiz. Ancak, burada göstereceğimiz yöntemlerin güvenlik açıkları barındırdığını ve üretim ortamlarında direkt olarak kullanılmaması gerektiğini önemle vurgulamak isteriz. Özellikle kullanıcıdan gelen verileri doğrudan sorguya dahil etmek, SQL enjeksiyon saldırılarına davetiye çıkarır. Bir sonraki bölümde bu güvenlik sorununu nasıl aşacağımızı detaylıca ele alacağız.

select sorgusu (veri okuma)

Veritabanından veri çekmek için SELECT sorguları kullanılır. Belirli koşullara göre veya tüm verileri seçmek mümkündür.


<?php
// $conn PDO bağlantı nesnesi daha önceki bölümden geliyor varsayılmıştır.

// Tüm kullanıcıları seçme
$stmt = $conn->query("SELECT id, ad, email FROM kullanicilar");
$users = $stmt->fetchAll();

echo "<h4>Tüm Kullanıcılar (SELECT)</h4>";
echo "<ul>";
foreach ($users as $user) {
    echo "<li>ID: " . $user['id'] . ", Ad: " . $user['ad'] . ", Email: " . $user['email'] . "</li>";
}
echo "</ul>";

// Belirli bir kullanıcıyı ID'ye göre seçme (Güvenli değil!)
$user_id = 1; // Genellikle bu değer kullanıcı girdisinden gelir
$stmt = $conn->query("SELECT id, ad, email FROM kullanicilar WHERE id = $user_id");
$user = $stmt->fetch();

if ($user) {
    echo "<h4>Belirli Kullanıcı (ID: $user_id - Güvenli Değil!)</h4>";
    echo "<p>ID: " . $user['id'] . ", Ad: " . $user['ad'] . ", Email: " . $user['email'] . "</p>";
} else {
    echo "<p>Kullanıcı bulunamadı.</p>";
}
?>

query() metodu, doğrudan sorguyu çalıştırır ve bir PDOStatement nesnesi döndürür. fetchAll() tüm sonuçları, fetch() ise tek bir satırı çeker.

insert sorgusu (veri ekleme)

Veritabanına yeni kayıtlar eklemek için INSERT INTO sorgusu kullanılır. Aşağıdaki örnekte, kullanıcıdan gelebilecek verileri doğrudan sorguya eklemenin ne kadar riskli olduğunu göreceksiniz.


<?php
// Yeni kullanıcı verileri (Örnek: kullanıcıdan alınmış)
$new_ad = "Ayşe Yılmaz";
$new_email = "ayse.yilmaz@example.com";
$new_password = "parola123"; // Şifreler asla düz metin olarak saklanmamalıdır!

// Güvenli olmayan INSERT sorgusu
$sql_insert = "INSERT INTO kullanicilar (ad, email, parola) VALUES ('$new_ad', '$new_email', '$new_password')";
$affected_rows = $conn->exec($sql_insert); // exec() ile doğrudan sorgu çalıştırılır

if ($affected_rows !== false) {
    echo "<p>Başarıyla eklenen kayıt sayısı (Güvenli Değil!): " . $affected_rows . "</p>";
} else {
    echo "<p>Ekleme işlemi başarısız oldu.</p>";
}
?>

exec() metodu, bir sorguyu doğrudan çalıştırır ve etkilenen satır sayısını döndürür (SELECT sorguları için uygun değildir). Dikkat edin, şifreler asla düz metin olarak saklanmamalıdır; bu sadece örnek amaçlıdır.

update sorgusu (veri güncelleme)

Mevcut verileri değiştirmek için UPDATE sorguları kullanılır. WHERE koşulu belirtilmezse, tablodaki tüm kayıtlar güncellenir, ki bu çok tehlikelidir.


<?php
// Güncellenecek veriler (Örnek: kullanıcıdan alınmış)
$update_email = "ayse.yeni@example.com";
$user_to_update_id = 2; // Genellikle kullanıcı girdisinden gelir

// Güvenli olmayan UPDATE sorgusu
$sql_update = "UPDATE kullanicilar SET email = '$update_email' WHERE id = $user_to_update_id";
$affected_rows = $conn->exec($sql_update);

if ($affected_rows !== false) {
    echo "<p>Başarıyla güncellenen kayıt sayısı (Güvenli Değil!): " . $affected_rows . "</p>";
} else {
    echo "<p>Güncelleme işlemi başarısız oldu.</p>";
}
?>

delete sorgusu (veri silme)

Veritabanından kayıt silmek için DELETE FROM sorgusu kullanılır. UPDATE'de olduğu gibi, WHERE koşulu belirtilmezse tablodaki tüm kayıtlar silinir!


<?php
// Silinecek kullanıcı ID'si (Örnek: kullanıcıdan alınmış)
$user_to_delete_id = 3; // Genellikle kullanıcı girdisinden gelir

// Güvenli olmayan DELETE sorgusu
$sql_delete = "DELETE FROM kullanicilar WHERE id = $user_to_delete_id";
$affected_rows = $conn->exec($sql_delete);

if ($affected_rows !== false) {
    echo "<p>Başarıyla silinen kayıt sayısı (Güvenli Değil!): " . $affected_rows . "</p>";
} else {
    echo "<p>Silme işlemi başarısız oldu.</p>";
}
?>

Gördüğünüz gibi, bu sorguların hepsi basit ve anlaşılırdır. Ancak, $user_id, $new_ad, $update_email gibi değişkenlerin kullanıcıdan geldiğini varsayarsak, bu yöntemler ciddi güvenlik riskleri taşır. Örneğin, bir saldırgan $user_id yerine '1 OR 1=1' gibi bir değer gönderdiğinde, WHERE koşulu her zaman doğru olacak ve beklenmedik sonuçlar (tüm kayıtların silinmesi, tüm verilerin çekilmesi vb.) ortaya çıkabilecektir. Bu nedenle, bir sonraki bölümde anlatacağımız prepared statement'lar vazgeçilmezdir.

Güvenli veri işlemleri: prepared statements

Bir önceki bölümde gösterdiğimiz SELECT, INSERT, UPDATE ve DELETE sorgularının, kullanıcıdan gelen verileri doğrudan sorgu dizisine eklemesi durumunda ne kadar tehlikeli olabileceğini vurgulamıştık. Bu yöntem, SQL enjeksiyonu adı verilen yaygın ve yıkıcı bir web güvenlik zafiyetine yol açar. SQL enjeksiyonu, kötü niyetli kullanıcıların veritabanı sorgularını manipüle ederek hassas verilere erişmesine, verileri değiştirmesine veya hatta silmesine olanak tanır.

Bu tür saldırılardan korunmanın en etkili yolu, prepared statement'lar (hazırlanmış ifadeler) kullanmaktır. Prepared statement'lar, sorgu yapısı ile sorguya eklenecek veriyi birbirinden ayırır. Veritabanı sistemi, sorgu yapısını önceden "hazırlar" ve ardından parametre olarak gönderilen verileri bu yapıya güvenli bir şekilde bağlar. Bu süreçte, veriler asla doğrudan SQL kodu olarak yorumlanmaz, bu da SQL enjeksiyon saldırılarını imkansız hale getirir.

prepared statements ile select sorgusu

Prepared statement kullanarak bir SELECT sorgusu çalıştırmak için önce sorguyu parametre yer tutucuları (:param_adı veya ?) ile hazırlarız, ardından bu yer tutuculara değerleri bağlarız.


<?php
// $conn PDO bağlantı nesnesi

// ID'si 1 olan kullanıcıyı güvenli bir şekilde seçme
$user_id_safe = 1;

$stmt = $conn->prepare("SELECT id, ad, email FROM kullanicilar WHERE id = :id");
$stmt->bindParam(':id', $user_id_safe, PDO::PARAM_INT); // Parametreyi bağla ve tipini belirt
$stmt->execute(); // Sorguyu çalıştır

$user_safe = $stmt->fetch();

if ($user_safe) {
    echo "<h4>Belirli Kullanıcı (Prepared Statement ile ID: $user_id_safe)</h4>";
    echo "<p>ID: " . $user_safe['id'] . ", Ad: " . $user_safe['ad'] . ", Email: " . $user_safe['email'] . "</p>";
} else {
    echo "<p>Kullanıcı bulunamadı.</p>";
}

// Tüm kullanıcıları güvenli bir şekilde çekme (parametreye ihtiyaç yoksa yine de prepare kullanmak iyi bir pratiktir)
$stmt_all = $conn->prepare("SELECT id, ad, email FROM kullanicilar");
$stmt_all->execute();
$all_users_safe = $stmt_all->fetchAll();

echo "<h4>Tüm Kullanıcılar (Prepared Statement ile)</h4>";
echo "<ul>";
foreach ($all_users_safe as $user) {
    echo "<li>ID: " . $user['id'] . ", Ad: " . $user['ad'] . ", Email: " . $user['email'] . "</li>";
}
echo "</ul>";
?>

Burada prepare() metodu ile sorguyu hazırladık. bindParam() ile :id yer tutucusunu $user_id_safe değişkenine bağladık ve değişkenin bir tam sayı (PDO::PARAM_INT) olduğunu belirttik. Bu tip belirtme, ek bir güvenlik katmanı sağlar. Son olarak execute() ile sorguyu çalıştırdık.

prepared statements ile insert sorgusu

Veri eklerken de aynı güvenli yaklaşımı benimsemeliyiz.


<?php
// Yeni kullanıcı verileri (güvenli bir şekilde)
$new_ad_safe = "Can Demir";
$new_email_safe = "can.demir@example.com";
$new_password_hash = password_hash("güvenliParola456", PASSWORD_DEFAULT); // Şifreler her zaman hash'lenmeli!

$stmt = $conn->prepare("INSERT INTO kullanicilar (ad, email, parola) VALUES (:ad, :email, :parola)");
$stmt->bindParam(':ad', $new_ad_safe);
$stmt->bindParam(':email', $new_email_safe);
$stmt->bindParam(':parola', $new_password_hash); // Hash'lenmiş şifreyi bağla
$stmt->execute();

if ($stmt->rowCount() > 0) {
    echo "<p>Başarıyla eklenen kayıt (Prepared Statement ile).</p>";
} else {
    echo "<p>Ekleme işlemi başarısız oldu.</p>";
}
?>

password_hash() fonksiyonunu kullanarak şifreleri asla düz metin olarak veritabanında saklamadığımıza dikkat edin. Bu, modern web güvenliğinin temel bir ilkesidir.

prepared statements ile update sorgusu

Veri güncellerken de aynı yöntem geçerlidir.


<?php
// Güncellenecek veriler (güvenli bir şekilde)
$update_email_safe = "can.yeni@example.com";
// Önceki INSERT'ten gelen ID'yi alalım veya belirli bir ID kullanalım.
// Burası için basitçe bir ID atayalım, gerçekte lastInsertId() veya başka bir kaynaktan alınır.
$user_to_update_id_safe = 4; // Örnek ID

$stmt = $conn->prepare("UPDATE kullanicilar SET email = :email WHERE id = :id");
$stmt->bindParam(':email', $update_email_safe);
$stmt->bindParam(':id', $user_to_update_id_safe, PDO::PARAM_INT);
$stmt->execute();

if ($stmt->rowCount() > 0) {
    echo "<p>Başarıyla güncellenen kayıt (Prepared Statement ile).</p>";
} else {
    echo "<p>Güncelleme işlemi başarısız oldu veya hiçbir değişiklik yapılmadı.</p>";
}
?>

prepared statements ile delete sorgusu

Veri silerken de prepared statement kullanmak, yanlış kayıtların silinmesini önler.


<?php
// Silinecek kullanıcı ID'si (güvenli bir şekilde)
$user_to_delete_id_safe = 5; // Örnek ID

$stmt = $conn->prepare("DELETE FROM kullanicilar WHERE id = :id");
$stmt->bindParam(':id', $user_to_delete_id_safe, PDO::PARAM_INT);
$stmt->execute();

if ($stmt->rowCount() > 0) {
    echo "<p>Başarıyla silinen kayıt (Prepared Statement ile).</p>";
} else {
    echo "<p>Silme işlemi başarısız oldu veya böyle bir kayıt bulunamadı.</p>";
}
// Bağlantıyı kapatma (gerekiyorsa)
$conn = null;
?>

bindParam() yerine bindValue() da kullanabilirsiniz. bindParam() referansla çalışırken, bindValue() değeri kopyalar. Genellikle bindParam() daha esnektir çünkü parametre değişkeni sorgu çalıştırılana kadar güncellenebilir. Ancak basit durumlarda ikisi de benzer şekilde çalışır. execute() metodu için doğrudan bir dizi geçirme ($stmt->execute([':id' => $user_id])) de mümkündür ve genellikle daha kısadır.

Özetle, prepared statement'lar sadece güvenlik için değil, aynı zamanda tekrarlanan sorguların performansını artırmak için de önemlidir çünkü veritabanı sunucusu sorgu yapısını bir kez ayrıştırır ve optimize eder.

Bu rehberde, PHP ve MySQL entegrasyonunun temel adımlarını detaylıca inceledik. Modern web uygulamaları için vazgeçilmez olan veritabanı bağlantısını PDO uzantısı ile kurmayı, hata yönetimiyle birlikte öğrendik. Ardından, veritabanı üzerinde temel CRUD operasyonları olan SELECT, INSERT, UPDATE ve DELETE sorgularını nasıl yazacağımızı gösterdik. Özellikle, bu sorguları doğrudan kullanıcı girdisiyle çalıştırmanın doğuracağı ciddi güvenlik risklerini vurguladık. Rehberin en önemli bölümünde ise, SQL enjeksiyon saldırılarından korunmanın altın kuralı olan prepared statement kullanımını her bir CRUD operasyonu için adım adım uyguladık. Güvenliğin, bir web uygulamasının geliştirilmesinde temel bir öncelik olduğunu bir kez daha hatırlatırız. Bu bilgiler ışığında, edindiğiniz sağlam temelle daha güvenli ve dinamik web uygulamaları geliştirmeye başlayabilirsiniz. Pratik yapmak ve sürekli öğrenmek, bu alandaki ustalığınızı pekiştirecektir.

Resim Sahibi: panumas nikhomkhai
https://www.pexels.com/@cookiecutter

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir