Gereksinimler ve Ön Hazırlık
Bu rehberde örneklerimizi PHP 8.4 ve PDO (PHP Data Objects) kütüphanesi üzerinden kurgulayacağız. PDO, farklı veritabanı sistemlerine (MySQL, PostgreSQL, SQLite) aynı arayüzle bağlanmanıza olanak tanıyan, modern ve güvenli bir veritabanı soyutlama katmanıdır.
- PHP 8.4 veya üzeri bir çalışma ortamı.
- MySQL 8.0 veya üzeri bir veritabanı sunucusu.
- PDO eklentisinin sunucuda aktif olması.
- Temel düzeyde SQL bilgisi ve OOP (Sınıflar, Arayüzler) hakimiyeti.
Adım 1: Veritabanı Bağlantısını Soyutlamak
Doğrudan her dosyada veritabanı bağlantısı açmak yerine, bir "Database" sınıfı oluşturarak bağlantı yönetimini merkezileştirmeliyiz. Bu, veritabanı bilgilerini tek bir yerden yönetmenizi sağlar.
class Database {
private static ?PDO $instance = null;
public static function getConnection(): PDO {
if (self::$instance === null) {
$dsn = "mysql:host=localhost;dbname=uygulama_db;charset=utf8mb4";
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
];
self::$instance = new PDO($dsn, 'db_kullanici', 'guclu_sifre', $options);
}
return self::$instance;
}
}
Yukarıdaki kod bloğunda Singleton tasarım desenini kullanarak uygulamanın her yerinde aynı veritabanı bağlantısının kullanılmasını sağlıyoruz. ATTR_EMULATE_PREPARES => false ayarı, SQL injection saldırılarına karşı veritabanı seviyesinde gerçek hazırlıklı ifadelerin kullanılmasını zorunlu kılar.
Adım 2: Veri Erişim Arayüzü (Interface) Oluşturma
Veri erişim katmanını esnek kılmak için arayüzler (interfaces) kullanmalısınız. Bu, veritabanı işlemlerini standartlaştırır ve birim testleri (unit testing) yazarken veritabanını taklit etmenize (mocking) olanak tanır.
interface UserRepositoryInterface {
public function findById(int $id): ?array;
public function save(array $data): bool;
}
Bu arayüz, UserRepository sınıfımızın hangi metodları içermesi gerektiğini belirler. Kodunuzu bu sözleşmeye uygun yazmak, projenin ilerleyen aşamalarında karmaşıklığı azaltır.
Adım 3: Veri Erişim Sınıfını Yazma
Şimdi, arayüzü uygulayan ve gerçek veritabanı işlemlerini gerçekleştiren sınıfımızı oluşturalım. Burada dikkat etmemiz gereken en önemli konu, verileri doğrudan sorgu içine gömmek yerine prepared statements (hazırlanmış ifadeler) kullanmaktır.
class UserRepository implements UserRepositoryInterface {
private PDO $db;
public function __construct(PDO $db) {
$this->db = $db;
}
public function findById(int $id): ?array {
$stmt = $this->db->prepare("SELECT * FROM kullanicilar WHERE id = :id");
$stmt->execute(['id' => $id]);
return $stmt->fetch() ?: null;
}
public function save(array $data): bool {
$stmt = $this->db->prepare("INSERT INTO kullanicilar (ad, email) VALUES (:ad, :email)");
return $stmt->execute(['ad' => $data['ad'], 'email' => $data['email']]);
}
}
Bu sınıf, veritabanı bağlantısını dışarıdan (Dependency Injection) alır. Bu sayede test edilebilirliği artırırız. :id, :ad gibi yer tutucular kullanarak SQL injection riskini tamamen ortadan kaldırıyoruz.
Adım 4: Veri Erişim Katmanını Kullanma
Oluşturduğumuz yapıyı artık iş mantığı katmanında kullanabiliriz. Bu yaklaşım, iş mantığının veritabanının nasıl çalıştığından haberdar olmamasını sağlar.
$db = Database::getConnection();
$userRepo = new UserRepository($db);
$kullanici = $userRepo->findById(1);
if ($kullanici) {
echo "Hoş geldiniz, " . htmlspecialchars($kullanici['ad']);
}
Burada htmlspecialchars fonksiyonunu kullanarak, veritabanından gelen veriyi ekrana basarken XSS (Cross-Site Scripting) saldırılarına karşı önlem alıyoruz. Veri erişim katmanı, veriyi ham halde getirmeli; güvenlik filtrelemesi ise genellikle sunum katmanında yapılmalıdır.
Veri Erişim Yöntemlerinin Karşılaştırılması
| Yöntem | Avantaj | Dezavantaj |
|---|---|---|
| PDO (Native) | Hızlı, esnek, güvenli | Daha fazla kod yazımı gerektirir |
| ORM (Örn: Eloquent) | Hızlı geliştirme, nesne odaklı | Performans maliyeti, öğrenme eğrisi |
| Query Builder | Okunabilir, güvenli | ORM kadar esnek değil |
Kritik Güvenlik ve Performans Uyarıları
Önemli: Veritabanı şifrelerini asla kod içinde düz metin olarak saklamayın. .env dosyaları veya güvenli kasa (vault) sistemleri kullanın. Ayrıca, üretim ortamında (production) hata mesajlarını kullanıcıya göstermeyin; sadece log dosyalarına kaydedin.
Veritabanı performansını artırmak için, sık sorgulanan sütunlara mutlaka INDEX eklediğinizden emin olun. Ayrıca, SELECT * yerine sadece ihtiyacınız olan sütunları seçmek, ağ trafiğini ve bellek kullanımını optimize eder.
Sıkça Sorulan Sorular
Veri erişim katmanı neden gereklidir?
Veritabanı bağımlılığını azaltmak, kodun test edilebilirliğini artırmak ve güvenlik açıklarını (SQL Injection gibi) merkezi bir noktadan yönetmek için gereklidir.
SQL Injection nedir ve nasıl engellenir?
Kullanıcıdan alınan verilerin SQL sorgusuna doğrudan eklenmesiyle oluşan bir açıktır. Hazırlanmış ifadeler (Prepared Statements) kullanarak engellenir.
PDO yerine ORM kullanmalı mıyım?
Projenin büyüklüğüne bağlıdır. Küçük ve orta ölçekli projelerde PDO yeterlidir; ancak karmaşık ilişkiler içeren büyük projelerde bir ORM (Object Relational Mapper) kullanmak üretkenliği artırır.
Veri erişim katmanında hata yönetimi nasıl olmalı?
PDO'yu ERRMODE_EXCEPTION modunda kullanarak hataları try-catch blokları ile yakalamalı ve uygun loglama mekanizmalarıyla kayıt altına almalısınız.
İndeksleme performansı nasıl etkiler?
Doğru indeksleme, veritabanı motorunun tüm tabloyu taramak yerine doğrudan ilgili satıra gitmesini sağlar, bu da sorgu sürelerini milisaniyelere indirir.
Veri Erişim Katmanında Birim Testleri (Unit Testing)
Veri erişim katmanınızı (DAL) oluşturduktan sonra, veritabanı işlemlerinin beklenen sonuçları döndürdüğünden emin olmak için test süreçlerini otomatize etmelisiniz. Birim testleri, veritabanına doğrudan bağlanmak yerine "Mock" nesneler kullanarak iş mantığınızın doğruluğunu denetlemenize olanak tanır.
PHPUnit kullanarak bir veri erişim sınıfını nasıl test edebileceğinize dair temel bir örnek:
use PHPUnit\Framework\TestCase;
class UserRepositoryTest extends TestCase {
public function testGetUserByIdReturnsCorrectData() {
// Mock nesnesi oluşturma
$mockDb = $this->createMock(DatabaseConnection::class);
// Beklenen davranış
$mockDb->method('prepare')->willReturn($stmt = $this->createMock(PDOStatement::class));
$stmt->method('fetch')->willReturn(['id' => 1, 'username' => 'testuser']);
$repo = new UserRepository($mockDb);
$user = $repo->findById(1);
$this->assertEquals('testuser', $user['username']);
}
}
Veri Erişim Katmanında Transaction Yönetimi
Birden fazla veritabanı işleminin bir bütün olarak gerçekleşmesi gereken durumlarda (örneğin; bir sipariş oluşturulurken hem stoktan düşülmesi hem de fatura kaydı açılması), Transaction kullanımı zorunludur. Hata durumunda tüm işlemlerin geri alınması (rollback), veritabanı bütünlüğünü korur.
Aşağıdaki yapı, veri erişim katmanınızda atomik işlemleri yönetmenize yardımcı olur:
public function processOrder(array $orderData) {
try {
$this->db->beginTransaction();
$this->saveOrder($orderData);
$this->updateInventory($orderData['items']);
$this->db->commit();
} catch (Exception $e) {
$this->db->rollBack();
error_log("İşlem başarısız: " . $e->getMessage());
throw new Exception("Sipariş işlenirken hata oluştu.");
}
}
Transaction Kullanırken Dikkat Edilmesi Gerekenler
- İzolasyon Seviyeleri: Veritabanı motorunuzun (InnoDB gibi) desteklediği izolasyon seviyelerini projenizin ihtiyacına göre yapılandırın.
- Kilitlenme (Deadlock): Aynı anda çok sayıda işlemin aynı satırlara erişmeye çalışması durumunda oluşabilecek kilitlenmeleri yönetmek için hata yakalama bloklarını (try-catch) mutlaka kullanın.
- Süre Sınırı: Transaction bloklarını mümkün olduğunca kısa tutun; uzun süren işlemler veritabanı performansını doğrudan düşürür.
Sonuç
Sql & Veritabanı ile uygulamalar için veri erişim katmanı oluşturmak, profesyonel bir yazılım geliştirme sürecinin temel taşıdır. Bu rehberde öğrendiğiniz Singleton bağlantı yönetimi, Arayüz kullanımı ve Hazırlanmış ifadeler ile veritabanı işlemlerinizi güvenli ve sürdürülebilir bir yapıya kavuşturabilirsiniz. Bir sonraki adım olarak, veritabanı işlemlerinde verimliliği artırmak için Repository Pattern ve Unit of Work desenlerini derinlemesine incelemenizi öneririm.
Yasal Uyarı: Bu rehberdeki kod örnekleri eğitim amaçlıdır. Yazılım güvenliği sorumluluğu geliştiriciye aittir. Üretim ortamına almadan önce tüm girdileri doğrulamalı (validation) ve sanitizasyon işlemlerini eksiksiz yapmalısınız.


Yorumlar (0)
Yorum Yaz