İçeriğe geç
academia.sh

Ders 04 / 15

Veritabanı Erişim Katmanı

PDO ile bağlantı, hata kipi, hazır deyim, adlı ve konumsal yer tutucular ve fetch kipleri kurulur; bağlantı ayarı ortamdan gelirse dönen değerin tipini kimin değiştirebildiği ölçülür.

Yayın Güncelleme

Bu derste

  • PDO ile bağlantı kurmak, hata kipini seçmek ve hazır deyimi adlı ve konumsal yer tutucularla çalıştırmak
  • Fetch kiplerinin aynı satırı hangi biçimde döndürdüğünü ayırt etmek
  • Dönen değerin tipini hangi tarafın değiştirebildiğini ölçmek
  • Açık tip dönüşümünün sonucu ortamdan sunucuya taşıdığını göstermek
İçindekiler

Önceki derste ayarın koddan değil ortamdan geldiği durumu ölçtük: aynı kod, farklı bir dağıtımda farklı davranabiliyordu. Veritabanı bağlantısı bu durumun en sık görüldüğü yerdir. Bağlantı ayarları kodun dışında tutulur ve dağıtımı yapan taraf onları değiştirebilir. Soru şudur: bağlantı ayarı ortamdan gelirse veritabanı ne tip döndürüyor?

Bu dersin ölçü ekseni, bir sonucu kimin değiştirebildiğidir. Kodun dışında üç taraf vardır ve karar sırası sabittir: önce istemci, sonra üçüncü taraf, sonra ortam. Hiçbiri sonucu değiştiremiyorsa sonuç sunucuya aittir. Sıra saldırı yüzeyinin genişliğini izler: istemci her isteği gönderen herkestir, üçüncü taraf yalnız bağlanılan taraflardır, ortam ise yalnız dağıtımı yapanın elindedir. Bu derste ağırlıklı taraf ortamdır. Çekirdek aşağıdaki dosyadır; her derste aynen verilir ve değiştirilmez.

<?php
// taraf.php — bu sonucu kim değiştirebiliyor?
declare(strict_types=1);

// Kodun dışındaki taraflar, karar sırasıyla: her istek, her dış çağrı, her dağıtım.
const DIS_TARAFLAR = ['istemci', 'üçüncü taraf', 'ortam'];

/** Sonucu bir kez sapmasız, sonra her dış tarafın sapmasıyla ayrı ayrı hesaplar. */
function sina(callable $sonuc, array $sapmalar): array {
    $taban = $sonuc([]);
    $degisti = [];
    foreach (DIS_TARAFLAR as $taraf) {
        if (array_key_exists($taraf, $sapmalar)) {
            $degisti[$taraf] = $sonuc([$taraf => $sapmalar[$taraf]]) !== $taban;
        }
    }
    return $degisti;
}

/** Sonucu değiştirebilen ilk dış taraf; hiçbiri değiştiremiyorsa sunucu. */
function taraf(array $degisti): string {
    foreach (DIS_TARAFLAR as $t) {
        if ($degisti[$t] ?? false) return $t;
    }
    return 'sunucu';
}

function tarafRaporu(string $ad, callable $sonuc, array $sapmalar): string {
    $degisti = sina($sonuc, $sapmalar);
    $satirlar = [];
    foreach ($degisti as $t => $d) $satirlar[] = $t . ($d ? ' değiştirdi' : ' değiştiremedi');
    $sonTaraf = taraf($degisti);
    echo "$ad → $sonTaraf\n    ", implode(' · ', $satirlar), "\n";
    return $sonTaraf;
}

VE1. Veritabanı bellekteki bir veritabanıdır ve bağlantı her ölçümde yeniden kurulur; bağlantı kapanınca veri yok olur, bu yüzden her ölçüm aynı iki üyeyle başlar. VE2. Ortamın sapması bir bağlantı ayarı olarak modellenir: ayar dizisini dağıtımı yapan taraf verir, kod kendisi yazmaz. VE3. İstemci yalnız üye adını gönderir; sapması ayse yerine mehmet göndermesidir. VE4. Sonuç değer değil, puan sütununun PHP’deki tipidir.

Bağlantı ve Hazır Deyim

Veri erişim katmanı, uygulamanın veritabanıyla konuştuğu tek yerdir; PHP’de bu işi PDO (PHP Data Objects) üstlenir. Katmanın ne işe yaradığı veritabanı derslerinde anlatıldı; burada yalnızca PDO’nun onu nasıl somutlaştırdığına bakılır. Aşağıdaki dosya bağlantıyı kurar ve iki üyelik bir tablo hazırlar. Çıktısı yoktur, yalnız diske yazılır; sonraki bloklar onu require ile çeker.

<?php
// baglanti.php — bellekteki veritabanına bağlanır, üye tablosunu kurar
declare(strict_types=1);

function baglan(array $ayarlar = []): PDO {
    $pdo = new PDO('sqlite::memory:', options: $ayarlar);
    $pdo->exec('CREATE TABLE uyeler (id INTEGER PRIMARY KEY, ad TEXT, puan INTEGER)');
    $pdo->exec("INSERT INTO uyeler (ad, puan) VALUES ('ayse', 7), ('mehmet', 3)");
    return $pdo;
}

/** Üyenin puanının PHP'de hangi tipte döndüğünü söyler. */
function puanTipi(string $ad, array $ayarlar, bool $acikDonusum): string {
    $pdo = baglan($ayarlar);
    $hazir = $pdo->prepare('SELECT puan FROM uyeler WHERE ad = :ad');
    $hazir->execute(['ad' => $ad]);
    $puan = $hazir->fetchColumn();
    return get_debug_type($acikDonusum ? (int) $puan : $puan);
}

Hata kipi (error mode), PDO’nun bir hatayı nasıl bildireceğini belirler. Üç seçenek vardır: sessiz kip hatayı yalnızca bir dönüş değeri ve hata kodu olarak bırakır, uyarı kipi bir uyarı üretir, istisna kipi bir PDOException fırlatır. PHP 8.0’dan beri varsayılan istisna kipidir; bu, bir sorgunun başarısız olmasının akışı kesmesi ve gözden kaçmaması demektir.

Hazır deyim (prepared statement), sorgu metnini ve değerleri iki ayrı adımda gönderir. prepare() sorgu metnini hazırlar; execute() değerleri yer tutucuların yerine bağlar. Yer tutucu iki biçimde yazılır: adlı yer tutucu (:alt) değeri adıyla, konumsal yer tutucu (?) sırasıyla eşler. Değer hiçbir zaman sorgu metninin içine yazılmaz. Bu ayrımın girdi güvenliğine ne kattığı Güvenlik konusunun işidir; burada yalnızca mekanizma kurulur.

Fetch kipi (fetch mode), satırın PHP’ye hangi biçimde döneceğini seçer. FETCH_ASSOC sütun adlarıyla bir dizi, FETCH_NUM sıra numaralarıyla bir dizi, FETCH_COLUMN yalnız ilk sütunun değerlerini verir.

<?php
// veritabani.php — hazır deyim, yer tutucular ve fetch kipleri
declare(strict_types=1);
require 'baglanti.php';

$pdo = baglan();
echo 'hata kipi istisna: ', var_export($pdo->getAttribute(PDO::ATTR_ERRMODE) === PDO::ERRMODE_EXCEPTION, true), "\n";

$adli = $pdo->prepare('SELECT ad, puan FROM uyeler WHERE puan > :alt');
$adli->execute(['alt' => 2]);
echo 'adlı, FETCH_ASSOC: ', json_encode($adli->fetchAll(PDO::FETCH_ASSOC), JSON_UNESCAPED_UNICODE), "\n";

$konumsal = $pdo->prepare('SELECT ad FROM uyeler WHERE puan > ?');
$konumsal->execute([5]);
echo 'konumsal, FETCH_COLUMN: ', json_encode($konumsal->fetchAll(PDO::FETCH_COLUMN)), "\n";

$sirali = $pdo->prepare('SELECT ad, puan FROM uyeler ORDER BY id');
$sirali->execute();
echo 'FETCH_NUM: ', json_encode($sirali->fetch(PDO::FETCH_NUM)), "\n";

try {
    $pdo->prepare('SELECT yok FROM uyeler')->execute();
} catch (PDOException $e) {
    echo 'geçersiz sorgu: ', get_class($e), "\n";
}
hata kipi istisna: true
adlı, FETCH_ASSOC: [{"ad":"ayse","puan":7},{"ad":"mehmet","puan":3}]
konumsal, FETCH_COLUMN: ["ayse"]
FETCH_NUM: ["ayse",7]
geçersiz sorgu: PDOException

Üç şey görülür. Varsayılan hata kipi istisnadır. Aynı iki satır adlı yer tutucuyla sütun adlı diziler olarak, konumsal yer tutucuyla yalnız ad olarak döner. Var olmayan bir sütunu soran sorgu sessizce boş dönmez, PDOException fırlatır.

Yazma Sorguları ve Etkilenen Satır

Okuma sorgusu satır döndürür; yazma sorgusu satır döndürmez, onun yerine bir etki bırakır. İki bilgi bu etkiyi anlatır. lastInsertId() en son eklenen satırın anahtarını verir. rowCount() yazma sorgusunun kaç satırı etkilediğini söyler. İkisi de sorgu başarıyla bittikten sonra okunur; hata kipi istisna olduğu için başarısız bir yazma zaten bu satıra hiç ulaşmaz.

bindValue() hazır deyime değeri tipiyle birlikte bağlamanın başka bir yoludur. execute() dizisi her değeri metin sayar; bindValue() ise üçüncü bağımsız değişkenle PDO::PARAM_INT gibi bir tip bildirir. Aşağıdaki dosya bir üye ekler, puanını günceller ve etkilenen satır sayısını okur.

<?php
// yazma.php — ekleme, güncelleme ve etkilenen satır
declare(strict_types=1);
require 'baglanti.php';

$pdo = baglan();

$ekle = $pdo->prepare('INSERT INTO uyeler (ad, puan) VALUES (:ad, :puan)');
$ekle->bindValue(':ad', 'zeynep');
$ekle->bindValue(':puan', 9, PDO::PARAM_INT);
$ekle->execute();
echo 'son anahtar: ', $pdo->lastInsertId(), "\n";

$guncelle = $pdo->prepare('UPDATE uyeler SET puan = puan + 1 WHERE puan > :alt');
$guncelle->execute(['alt' => 5]);
echo 'etkilenen satır: ', $guncelle->rowCount(), "\n";

$yok = $pdo->prepare('UPDATE uyeler SET puan = 0 WHERE ad = :ad');
$yok->execute(['ad' => 'kimse']);
echo 'olmayan üye, etkilenen satır: ', $yok->rowCount(), "\n";
son anahtar: 3
etkilenen satır: 2
olmayan üye, etkilenen satır: 0

Üçüncü satır bir hata değildir. Hiçbir satırla eşleşmeyen bir UPDATE başarıyla biter ve sıfır satır etkiler. Yazmanın bir etki bırakıp bırakmadığını anlamak için rowCount() değerine bakılır; istisna bu durumda fırlamaz.

Dönen Değerin Tipi

Sütun tipi veritabanında INTEGER olsa da PHP’ye hangi tipte geleceği bir bağlantı ayarına bağlıdır. PHP 8.1’den beri SQLite sürücüsünün varsayılan davranışında tam sayı sütunu int olarak döner. PDO::ATTR_STRINGIFY_FETCHES ayarı açıksa her değer string olarak döner. Kod aynı kalır, yalnız ayar değişir. Ayarı dağıtımı yapan taraf verdiği için sapma ortamdan gelir.

Sonuç, puan sütununun tipidir. İstemcinin sapması üye adını değiştirir; ama ayse de mehmet de aynı tipte bir puana sahiptir. Aşağıdaki dosya önce iki tipi gösterir, ardından taraf raporunu alır.

<?php
// tip.php — puan sütununun tipini kim değiştirebiliyor?
declare(strict_types=1);
require 'taraf.php';
require 'baglanti.php';

echo 'varsayılan: ', puanTipi('ayse', [], false), "\n";
echo 'metne çevirme açık: ', puanTipi('ayse', [PDO::ATTR_STRINGIFY_FETCHES => true], false), "\n";

tarafRaporu(
    'puan tipi',
    fn(array $s) => puanTipi($s['istemci'] ?? 'ayse', $s['ortam'] ?? [], false),
    ['istemci' => 'mehmet', 'ortam' => [PDO::ATTR_STRINGIFY_FETCHES => true]]
);
varsayılan: int
metne çevirme açık: string
puan tipi → ortam
    istemci değiştiremedi · ortam değiştirdi

İstemci tipi değiştiremez: hangi üyeyi sorarsa sorsun puan aynı tipte döner. Ortam değiştirebilir: ayar açıldığında aynı sorgu int yerine string getirir. Karar sırasına göre ilk değiştirebilen taraf ortamdır, bu yüzden ders ortama aittir. Dönen tip yalnızca bir yerleşim ayrıntısı gibi görünür, ama değer int bekleyen tipli bir parametreye verildiğinde strict_types altında string gelmesi bir TypeError fırlatır.

Sınırlayıcı Ölçüm

Aynı soruyu bir kez daha soralım, ama bu kez kod dönen değeri kendisi int’e çevirsin. Sorgu, yer tutucu ve ayar aynıdır; yalnızca (int) dönüşümü eklenmiştir. Ortamın sapması bağlantı ayarını yine değiştirir ve veritabanı yine string döndürür, ancak kod bunu sonuca ulaşmadan önce düzeltir.

<?php
// sinir.php — açık dönüşümle aynı ölçüm
declare(strict_types=1);
require 'taraf.php';
require 'baglanti.php';

tarafRaporu(
    'puan tipi, açık dönüşümle',
    fn(array $s) => puanTipi($s['istemci'] ?? 'ayse', $s['ortam'] ?? [], true),
    ['istemci' => 'mehmet', 'ortam' => [PDO::ATTR_STRINGIFY_FETCHES => true]]
);
puan tipi, açık dönüşümle → sunucu
    istemci değiştiremedi · ortam değiştiremedi

Sonuç ortamdan sunucuya taşındı. Ortam ayarı hâlâ değiştirebilir, ama değişikliğin sonuca etkisi kalmaz; çünkü sonucun tipini artık kodun kendi dönüşümü belirler. Her savunma düzeneği sahibi bu biçimde taşımaz, bazı sonuçlarda sahip değişmeden kalır. Bu örnekte ise dönüşüm sonucu kodun içine çeker.

Özet

  • PDO bağlantısı, hata kipi, hazır deyim ve fetch kipleri veri erişim katmanını oluşturur; varsayılan hata kipi istisnadır.
  • Hazır deyim sorgu metnini ve değerleri ayırır; yer tutucu adlı (:alt) ya da konumsal (?) yazılır.
  • Fetch kipi satırın biçimini seçer: sütun adlı dizi, sıralı dizi ya da tek sütun.
  • Dönen değerin tipi bir bağlantı ayarına bağlıdır ve ayar ortamdan geldiği için bu derste taraf ortamdır.
  • Kod değeri açıkça int’e çevirdiğinde ortamın sapması sonucu değiştiremez ve sonuç sunucuya geçer.

Sonraki Adım

Bağlantıyı ve tek bir sorguyu kurduk; ama bir iş çoğu zaman birden fazla yazmadan oluşur. İkinci yazma başarısız olduğunda ilki ne olur? Sonraki ders, yarıda kalan yazmayı işlemlerle ele alır.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ ↵ Esc gezin · aç · kapat