---
title: 'Nesne ve Tablo Bağlaması'
source: 'https://academia.sh/tr/kurslar/web-uygulama-cercevesi/nesne-ve-tablo-baglamasi'
course: 'Web Uygulama Çerçevesi'
language: tr
updated: '2026-10-21T05:03:39+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Tabloyu bir model sınıfına bağlayan küçük bir düzenek kurmak'
    - 'Kaydetme kancasının kayıt sırasında koştuğunu ölçmek'
    - 'İzin listesinde olmayan alanın aşırı atamada bağlanmadığını göstermek'
    - 'Toplu güncellemenin kaydetme kancasını çalıştırmadığını gerçek çıktıyla okumak'
---

# Nesne ve Tablo Bağlaması

Tabloyu PHP dizisi olarak tutan küçük bir model sınıfı kurulur; kaydetme kancasının kayıtta koştuğu, izin listesindeki olmayan alanın aşırı atamada bağlanmadığı ve toplu güncellemede kancanın bağlanmadığı ölçülür.

Form doğrulaması geçti ve kayıt adımı koştu. Peki kayıt bir model üzerinden yapıldığında, kaydedilirken yalnızca satır mı yazılır? Modelin kendi kancaları, erişimcileri ve alan kuralları da çalışır; hangilerinin çalıştığını görmek bu dersin konusudur.

Nesne–tablo bağlaması (object–relational mapping) bir sınıfı bir tabloya bağlayan düzenektir: sınıfın bir örneği tablonun bir satırıdır. Bu bağlamanın kaynakta ne doğurduğu M08/K06'daki aynı adlı derste ölçülmüştü; bu derste soru farklıdır: model kaydedilirken hangi parça bu istekte koştu. Karar sırası `sinifla()` fonksiyonunun kendisidir ve bu dersin ağırlıklı sınıfı `koştu`'dur; ölçümler `bağlanmadı` ile karşılaştırılır.

**GV8.** Tablo bir PHP dizisidir, yani veri katmanı bir dizi satırdan ibarettir; gerçek bir veri tabanı bağlantısı yoktur. **GV9.** Alan adları ASCII'dir ve `id` kayıt sırasında tablonun boyundan türetilir. **GV10.** Aşırı atama (mass assignment) karşıtı için `okunma` adlı nötr bir sayaç alanı kullanılır; alan yetki ya da rol taşımaz. **GV11.** Slug üretimi yalnızca ASCII harf ve rakamları korur.

Ölçüm düzeneği değişmeden gelir.

```php
<?php
// kostu.php — beklenen bir parça bu istekte koştu mu?
declare(strict_types=1);

final class Gunluk {
    public static array $yol = [];         // isteğin yoluna konan parçalar
    public static array $kostu = [];       // istek içinde çağrılanlar
    public static array $ertelenen = [];   // isteğin dışına bırakılanlar
    public static function yeniIstek(): void { self::$yol = self::$kostu = self::$ertelenen = []; }
    public static function yola(string $parca): void { self::$yol[] = $parca; }
    public static function kostu(string $parca): void { self::$kostu[] = $parca; }
    public static function ertele(string $parca): void { self::$ertelenen[] = $parca; }
}

function sinifla(string $parca): string {
    return match (true) {
        in_array($parca, Gunluk::$kostu, true) => 'koştu',
        in_array($parca, Gunluk::$ertelenen, true) => 'ertelendi',
        in_array($parca, Gunluk::$yol, true) => 'atlandı',
        default => 'bağlanmadı',
    };
}
```

## Modeli Kurmak

Model tabanı üç şey bilir: hangi tabloya yazdığını, hangi alanların toplu doldurulabildiğini ve kaydetme sırasında hangi kancayı çağıracağını. Yaşam döngüsü kancası (lifecycle hook) bir olayın öncesinde ya da sonrasında çağrılan yöntemdir; burada `kaydediliyor` kaydın tabloya yazılmasından hemen önce çağrılır. Erişimci (accessor) bir alanı okurken değeri dönüştüren yöntemdir; burada `okuOzetKisa`, `ozetKisa` adıyla okunur. Sorgu kapsamı (query scope) sık kullanılan bir süzgeci adlandırır; `yayinda()` yalnızca yayınlanmış satırları döndürür.

`doldur()` yalnızca izin listesindeki alanları alır. İzin listesinde olmayan bir alan için ne bir hata ne bir günlük kaydı vardır: alan, parçanın yoluna hiç konmaz. Bu dersin karşıt ölçümü bu sessizliğe dayanır.

```php
<?php
// model.php — tablo dizisi, kapsam, erişimci ve kancalı küçük bir model
declare(strict_types=1);
require_once 'kostu.php';

final class Veri {
    public static array $tablolar = ['yazilar' => []];
    public static function sifirla(): void { self::$tablolar = ['yazilar' => []]; }
}

abstract class Model {
    protected static string $tablo;
    protected static array $doldurulabilir = [];
    public array $ozellikler = [];

    public static function olustur(array $girdi): static {
        return (new static())->doldur($girdi)->kaydet();
    }

    public function doldur(array $girdi): static {
        foreach ($girdi as $alan => $deger) {
            if (in_array($alan, static::$doldurulabilir, true)) {
                Gunluk::yola("alan:$alan");
                Gunluk::kostu("alan:$alan");
                $this->ozellikler[$alan] = $deger;
            }
        }
        return $this;
    }

    public function kaydet(): static {
        Gunluk::yola('kanca:kaydediliyor');
        $this->kaydediliyor();
        $this->ozellikler['id'] = count(Veri::$tablolar[static::$tablo]) + 1;
        Veri::$tablolar[static::$tablo][] = $this->ozellikler;
        return $this;
    }

    protected function kaydediliyor(): void {}

    public static function toplu(array $degerler, array $kosul): int {
        $say = 0;
        foreach (Veri::$tablolar[static::$tablo] as &$satir) {
            if (array_intersect_assoc($kosul, $satir) === $kosul) {
                $satir = $degerler + $satir;
                $say++;
            }
        }
        return $say;
    }

    public function __get(string $ad): mixed {
        $erisimci = 'oku' . ucfirst($ad);
        return method_exists($this, $erisimci) ? $this->$erisimci() : ($this->ozellikler[$ad] ?? null);
    }
}

final class Yazi extends Model {
    protected static string $tablo = 'yazilar';
    protected static array $doldurulabilir = ['baslik', 'ozet', 'yayinda'];

    protected function kaydediliyor(): void {
        Gunluk::kostu('kanca:kaydediliyor');
        $baslik = strtolower((string) $this->ozellikler['baslik']);
        $this->ozellikler['slug'] = trim(preg_replace('/[^a-z0-9]+/', '-', $baslik), '-');
    }

    protected function okuOzetKisa(): string {
        return mb_substr((string) $this->ozellikler['ozet'], 0, 5) . '...';
    }

    public static function yayinda(): array {
        return array_values(array_filter(Veri::$tablolar['yazilar'], fn($s) => $s['yayinda'] ?? false));
    }
}

function raporla(array $parcalar): void {
    foreach ($parcalar as $parca) printf("  %-20s %s\n", $parca, sinifla($parca));
}
```

## Kaydetme Kancasının Koştuğunu Görmek

Ölçüm iki yazı oluşturur: biri yayında, öteki değil. Beklenen: kaydetme kancası `koştu`, başlıktan `ilk-yazi` slug'ı üretildi, kapsam yalnızca yayındaki satırı döndürür, erişimci özeti kısaltır. İzin listesindeki alanların üçü de `koştu`.

```php
<?php
// gozlem.php — kaydetme kancası, kapsam ve erişimci
require_once 'model.php';

Veri::sifirla();
Gunluk::yeniIstek();
$yazi = Yazi::olustur(['baslik' => 'Ilk Yazi', 'ozet' => 'Kisa ozet metni', 'yayinda' => true]);
Yazi::olustur(['baslik' => 'Taslak', 'ozet' => 'Henuz hazir degil', 'yayinda' => false]);
echo "slug={$yazi->slug}\n";
echo "ozetKisa={$yazi->ozetKisa}\n";
echo "yayinda=" . count(Yazi::yayinda()) . " / toplam=" . count(Veri::$tablolar['yazilar']) . "\n";
raporla(['alan:baslik', 'alan:ozet', 'alan:yayinda', 'kanca:kaydediliyor']);
```

```
slug=ilk-yazi
ozetKisa=Kisa ...
yayinda=1 / toplam=2
  alan:baslik          koştu
  alan:ozet            koştu
  alan:yayinda         koştu
  kanca:kaydediliyor   koştu
```

Kanca, kaydı yazan çağrının içinde kendiliğinden koştu; çağıranın bir şey yapması gerekmedi, slug kendiliğinden üretildi. Bu, kancanın yararı ve aynı zamanda kırılganlığıdır: davranış çağıranın göremediği bir yerde durur. Kapsam ve erişimci ayrı birer koşu kaydı bırakmadı, çünkü bunlar çağrılarak çalışan yöntemlerdir ve ölçülen şey bir adımın isteğe katılıp katılmadığıdır.

## Karşıt Ölçüm: İzin Listesinde Olmayan Alan

Aşırı atama, bir isteğin gövdesindeki alanların topluca modele aktarılmasıdır; izin listesi bu aktarımın sınırıdır. Ölçüm isteğe izin listesinde olmayan `okunma` alanını ekler. Beklenen: `okunma` alanı `bağlanmadı`, çünkü hiç yola konmadı; değer kayda girmez; hata yoktur.

```php
<?php
// asiri.php — izin listesinde olmayan alan aşırı atamada bağlanmaz
require_once 'model.php';

Veri::sifirla();
Gunluk::yeniIstek();
$yazi = Yazi::olustur(['baslik' => 'Ikinci', 'ozet' => 'Metin', 'yayinda' => true, 'okunma' => 999]);
echo "okunma kayitta " . (array_key_exists('okunma', Veri::$tablolar['yazilar'][0]) ? 'var' : 'yok') . "\n";
raporla(['alan:baslik', 'alan:okunma']);
```

```
okunma kayitta yok
  alan:baslik          koştu
  alan:okunma          bağlanmadı
```

`okunma` istekte vardı, kayıtta yok. Sınıf `atlandı` değil `bağlanmadı`: bir parça yola konup çağrılmazsa `atlandı` olurdu; burada izin listesi alanı yola koyma adımının dışında bıraktı. Hata verilmemesi, aşırı atamada sessizliğin hem koruyucu hem de aldatıcı olabileceği anlamına gelir. Alan neden girmedi sorusunun cevabı çıktıda değil izin listesindedir.

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

Kanca kayıt yolunda çalışır. Peki kayıt yolunu atlayan bir güncelleme yolu varsa? `toplu()` doğrudan tablo satırlarını değiştirir, `kaydet()` çağrılmaz. Ölçüm önce bir yazı oluşturur, sonra başlığını `toplu()` ile değiştirir. Beklenen: kanca `bağlanmadı` ve slug eski başlıkta kalır.

```php
<?php
// toplu.php — toplu güncelleme kaydetme kancasını çalıştırmaz
require_once 'model.php';

Veri::sifirla();
Yazi::olustur(['baslik' => 'Ilk Yazi', 'ozet' => 'Metin', 'yayinda' => true]);
Gunluk::yeniIstek();
$say = Yazi::toplu(['baslik' => 'Yeni Baslik'], ['id' => 1]);
$satir = Veri::$tablolar['yazilar'][0];
echo "guncellenen=$say\n";
echo "baslik={$satir['baslik']} slug={$satir['slug']}\n";
raporla(['kanca:kaydediliyor']);
```

```
guncellenen=1
baslik=Yeni Baslik slug=ilk-yazi
  kanca:kaydediliyor   bağlanmadı
```

Çıktıda kanca `bağlanmadı`: başlık değişti, slug değişmedi. Slug başlığa bağlı bir türetilmiş değerdir ve kanca atlandığında tutarsızlaşır. Bağlanmayan kanca hata vermez, bu yüzden yanıtta da görünmez. Bu sınıf tehlikelidir: davranışın beklendiği yerde hiçbir iz yoktur. Çözüm, değişikliğin kaydet yolundan geçirilmesidir ya da toplu güncellemenin türetilmiş alanı kendisinin yazmasıdır; hangisinin seçildiği çağıranın kararıdır.

## Özet

- Model, bir sınıfı bir tabloya bağlar; kapsam, erişimci ve kanca bu bağlamanın üstüne eklenir.
- Bu dersin ölçü sınıfı `koştu`'dur: kaydetme kancası kayıt yolunda kendiliğinden koştu ve başlıktan slug üretti.
- İzin listesinde olmayan alan aşırı atamada `bağlanmadı`: değer kayda girmedi ve hata da vermedi.
- Kayıt yolunu atlayan toplu güncellemede kanca `bağlanmadı` ve türetilmiş alan eski kaldı.
- Sessizlik koruma da olabilir aldatıcı da; neden kayda girmediği çıktıda değil izin listesindedir.

## Sonraki Adım

Model tek tabloyu ele aldı. Bir yazının yazarı başka tabloda durduğunda, o ilişki sorgusu ne zaman çalışır? Bir sonraki ders ilişkileri ve tembel yüklemeyi ölçer; ilişki sorgusunun ertelendiği sınıfı ele alır.
