---
title: 'Neden Çerçeve'
source: 'https://academia.sh/tr/kurslar/web-uygulama-cercevesi/neden-cerceve'
course: 'Web Uygulama Çerçevesi'
language: tr
updated: '2026-10-21T05:03:39+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Denetimin ters çevrilmesini, akışı betiğin yürüttüğü bir if zinciri ile çerçevenin çağırdığı bir kayıtlı eylemi karşılaştırarak açıklar'
    - 'Bir parçayı koştu, ertelendi, atlandı ya da bağlanmadı olarak sınıflayan kuralı ve karar sırasını okur'
    - 'Dört istek ve on iki beklenen parçalık çekirdek ölçümünü koşturup 4, 3, 3 ve 2 dağılımını yorumlar'
    - 'Sınıflamanın beklenen parçayı geliştiricinin adlandırmasına bağlı olduğunu ölçer'
---

# Neden Çerçeve

Akışı betiğin kendisinin yürüttüğü bir if zinciri ile akışı çerçevenin yürüttüğü bir rota kaydının karşılaştırılması; bir parçanın bu istekte koşup koşmadığını sınıflayan dört ad ve on iki parçalık ölçüm.

Nesneye Dayalı PHP ve Standartlar kursu kodu birden çok dosyaya ve birden çok yazara böldü ve bir tanım değişince kullananın ne yaşadığını ölçtü. Bu kurs kodu bir çerçeveye (framework) yerleştirir ve ilişkiyi tersine çevirir: kodu yazan taraf kendi parçalarını yazar, onları çerçeve çağırır. Denetimin bu biçimde ters çevrilmesi, çerçeveyle çalışırken en sık sorulan soruyu doğurur: yazılan parça neden koşmadı? Kurs yirmi bir derste her düzeneği tek bir soruyla ölçer: **bu parça bu istekte koştu mu?**

## Akışı Kim Yürütüyor

Çerçevesiz bir betikte akışı betiğin kendisi yürütür. Hangi isteğin hangi fonksiyonu çağıracağı, çağıranın yazdığı satırlarda durur.

```php
<?php
// elle.php — akışı betiğin kendisi yürütür
function form(): string { return 'boş form'; }
function liste(): string { return 'yazı listesi'; }

$yol = '/yazilar/yeni';

if ($yol === '/yazilar') {
    echo liste(), "\n";
} elseif ($yol === '/yazilar/yeni') {
    echo form(), "\n";
} else {
    echo "404\n";
}
```

```
boş form
```

**ÇT1.** `form()` çağrısını betiğin kendi `if` zinciri yaptı. Yeni bir yol eklemek zinciri düzenlemek demektir ve zincirin sırası, hangi fonksiyonun çağrılacağını belirleyen tek şeydir. Çerçevede bu ilişki tersine döner: yazan taraf `form` eylemini bir kalıpla birlikte **kaydeder**, kalıbı isteğe karşı deneyip eylemi çağıran kod çerçevenin içindedir. Yazılan parça artık çağıran değil çağrılandır. Çağrıyı çerçeve yaptığı için, parçanın çağrılıp çağrılmadığı da yazanın göremediği bir yerde karar verilir. Soru bu yüzden ölçülmeye değerdir.

## Ölçüm Çekirdeği

Ölçümün kendisi `kostu.php` dosyasında durur ve kurs boyunca değişmez. Her derste aynı yirmi iki satır yeniden verilir.

```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ı',
    };
}
```

`Gunluk` sınıfı üç liste tutar: isteğin **yoluna** konan parçalar, istek içinde **çağrılan** parçalar ve isteğin **dışına bırakılan** parçalar. `yeniIstek()` üçünü de boşaltır, her ölçüm onunla başlar. Bir düzenek kendini çekirdeğe üç çağrıyla bağlar: parçayı yola koyduğunda `Gunluk::yola()`, çağırdığında `Gunluk::kostu()`, isteğin dışına bıraktığında `Gunluk::ertele()`. `sinifla()` bir parçanın adını alır ve bu üç listeye **sırayla** bakar.

**ÇT2.** Karar sırası `sinifla()`'nın kendisidir. Parça `$kostu` listesindeyse **koştu**: istek sırasında çağrıldı ve yanıt onu bekledi. Değilse ve `$ertelenen` listesindeyse **ertelendi**: kaydedildi ama istekte koşmadı, yanıttan sonra ya da bir işçide koşacak. Değilse ve `$yol` listesindeyse **atlandı**: parça isteğin yolundaydı ama önündeki bir adım yanıtı erken verdi. Üçünde de yoksa **bağlanmadı**: hiçbir kayıt parçayı bu isteğe bağlamadı ve hata da verilmedi. Sıra bir kural olarak yazılmıştır; parça iki listede birden bulunsaydı ilk eşleşen kazanırdı. Bu derste hiçbir parça iki listede birden bulunmaz.

Bir koşul sınıflamanın parçasıdır: **beklenen parçayı geliştirici adlandırır.** Ölçülen şey çerçevenin bütün kayıtları değil, geliştiricinin bu istekte koşmasını beklediği parçalardır ve `sinifla()` o adı alıp ona ne olduğunu söyler. Bir dersin ölçtüğü her parça önceden adlandırılır.

İki ayrım baştan yapılmalıdır. Nesneye Dayalı PHP ve Standartlar kursunda bir tanım değişikliğinin kullananlara ulaşımı iki koşumla, öncesi ve sonrasıyla ölçüldü; burada tek bir koşum vardır ve sorulan, çağrının olup olmadığıdır. PHP Temelleri kursunda sessiz sınıfı bir aksaklığın hiçbir sinyal vermemesiydi; **bağlanmadı** ise bir aksaklık değil, bir eşleşme eksikliğidir: kayıt vardır, yalnız bu isteğe bağlanmamıştır. Sinyal ölçülmez, `Gunluk` ölçülür.

## Çerçeve Modeli

`cerceve.php` çerçevenin küçük bir modelidir. Bu kursta çerçeve ürün adıyla değil, bu model üzerinden öğretilir; yalnız bu ilk derste bütünüyle yazılır, sonraki dersler kendi düzeneklerinin dilimini kurar.

```php
<?php
// cerceve.php — küçük bir web uygulama çerçevesi modeli
declare(strict_types=1);
require_once 'kostu.php';

final class Istek {
    public array $parametre = [];
    public function __construct(
        public string $yontem, public string $yol,
        public array $govde = [], public bool $oturum = false,
    ) {}
}

final class Yanit {
    public function __construct(public int $durum, public string $govde = '') {}
}

final class NesneKabi {
    private array $tarifler = [];
    private array $tekiller = [];
    public function bagla(string $ad, Closure $tarif, bool $tekil = false): void {
        $this->tarifler[$ad] = [$tarif, $tekil];
    }
    public function al(string $ad): object {
        if (isset($this->tekiller[$ad])) return $this->tekiller[$ad];
        if (isset($this->tarifler[$ad])) {
            [$tarif, $tekil] = $this->tarifler[$ad];
            $nesne = $tarif($this);
            if ($tekil) $this->tekiller[$ad] = $nesne;
            return $nesne;
        }
        $bagimliliklar = [];
        foreach ((new ReflectionClass($ad))->getConstructor()?->getParameters() ?? [] as $p) {
            $bagimliliklar[] = $this->al($p->getType()->getName());
        }
        return new $ad(...$bagimliliklar);
    }
}

final class Kuyruk {
    private array $bekleyen = [];
    public function ekle(string $parca, Closure $is): void {
        Gunluk::ertele($parca);
        $this->bekleyen[] = [$parca, $is];
    }
    public function isle(): array {            // işçi: istekten bağımsız koşar
        $kosan = [];
        while ($kayit = array_shift($this->bekleyen)) {
            [$parca, $is] = $kayit;
            $is();
            $kosan[] = $parca;
        }
        return $kosan;
    }
}

final class Olaylar {
    private array $dinleyiciler = [];
    public function __construct(private Kuyruk $kuyruk) {}
    public function dinle(string $olay, string $parca, Closure $is, bool $kuyrukta = false): void {
        $this->dinleyiciler[$olay][] = [$parca, $is, $kuyrukta];
    }
    public function yay(string $olay): void {
        foreach ($this->dinleyiciler[$olay] ?? [] as [$parca, $is, $kuyrukta]) {
            if ($kuyrukta) { $this->kuyruk->ekle($parca, $is); continue; }
            Gunluk::kostu($parca);
            $is();
        }
    }
}

final class Uygulama {
    private array $rotalar = [];
    private array $sonra = [];             // yanıt gönderildikten sonra koşacaklar
    public function __construct(public NesneKabi $kap) {}
    public function rota(string $yontem, string $kalip, string $parca, Closure $eylem, array $ara = []): void {
        $this->rotalar[] = [$yontem, $kalip, $parca, $eylem, $ara];
    }
    public function yanittanSonra(string $parca, Closure $is): void {
        Gunluk::ertele($parca);
        $this->sonra[$parca] = $is;
    }
    private function eslestir(Istek $istek): ?array {
        foreach ($this->rotalar as $rota) {        // ilk eşleşen kazanır
            $desen = '#^' . preg_replace('#\{(\w+)\}#', '(?P<$1>[^/]+)', $rota[1]) . '$#';
            if ($rota[0] === $istek->yontem && preg_match($desen, $istek->yol, $m)) {
                $istek->parametre = array_filter($m, 'is_string', ARRAY_FILTER_USE_KEY);
                return $rota;
            }
        }
        return null;
    }
    public function isle(Istek $istek): Yanit {
        Gunluk::yeniIstek();
        $this->sonra = [];
        $rota = $this->eslestir($istek);
        if ($rota === null) return new Yanit(404);
        [, , $parca, $eylem, $ara] = $rota;
        foreach ([...array_column($ara, 0), $parca] as $p) Gunluk::yola($p);
        $hat = function (Istek $i) use ($parca, $eylem): Yanit {
            Gunluk::kostu($parca);
            return $eylem($i, $this->kap);
        };
        foreach (array_reverse($ara) as [$ad, $katman]) {
            $hat = function (Istek $i) use ($ad, $katman, $hat): Yanit {
                Gunluk::kostu($ad);
                return $katman($i, $hat);
            };
        }
        return $hat($istek);
    }
    public function bitir(): array {           // yanıt istemciye gitti
        foreach ($this->sonra as $is) $is();
        return array_keys($this->sonra);
    }
}
```

Dosya altı parçadan oluşur ve her sonraki ders bunlardan birini açar. `Istek` ve `Yanit` isteğin ve yanıtın taşıyıcılarıdır. `NesneKabi` (nesne kabı, object container) sınıf adından nesne kuran bir fabrikadır; kayıtsız bir sınıfı yapıcı parametrelerinin tipinden `ReflectionClass` ile kurar ve yalnız sınıf tipli parametreyi çözer. `Kuyruk` işleri isteğin dışına bırakır; `isle()` çağrısı işçidir ve model aynı süreçte çalışır. `Olaylar` bir olay adına kayıtlı dinleyicileri çağırır ya da kuyruğa koyar. `Uygulama` rotaları kaydeder, `eslestir()` ile kayıt sırasıyla dener ve **ilk eşleşen kazanır**, ardından ara katmanları iç içe geçmiş bir zincire sarıp çağırır.

**ÇT3.** Ters çevrilmiş denetim `isle()` içinde görünür. Eylem `$eylem($i, $this->kap)` satırında çağrılır, çağıran çerçevedir; ve `Gunluk::kostu($parca)` kaydı da eylemin kendisinde değil, çerçevenin çağrı satırının hemen önündedir. Ölçüm parçaların içine yazılmaz, parçaları çağıran düzeneğe yazılır: parça kendini koştu diye bildirmez, çerçeve onu çağırdığını bildirir.

## Dört İstek, On İki Parça

`istekler.php` bir nesne kabı kurar, üç rota ve üç dinleyici kaydeder ve dört isteği sırayla işler. Her isteğin beklenen parçaları istekle birlikte adlandırılmıştır.

```php
<?php
// istekler.php — dört istek, on iki beklenen parça
require 'cerceve.php';

$kap = new NesneKabi();
$kap->bagla(Kuyruk::class, fn() => new Kuyruk(), tekil: true);
$kap->bagla(Olaylar::class, fn(NesneKabi $k) => new Olaylar($k->al(Kuyruk::class)), tekil: true);
$uygulama = new Uygulama($kap);

$oturum = ['oturum', fn(Istek $i, Closure $sonraki) =>
    $i->oturum ? $sonraki($i) : new Yanit(302, '/giris')];
$dogrulama = ['dogrulama', fn(Istek $i, Closure $sonraki) =>
    ($i->govde['baslik'] ?? '') !== '' ? $sonraki($i) : new Yanit(422, 'baslik gerekli')];

$uygulama->rota('GET', '/yazilar/{id}', 'goster', fn(Istek $i) => new Yanit(200, "yazı {$i->parametre['id']}"));
$uygulama->rota('GET', '/yazilar/yeni', 'form', fn() => new Yanit(200, 'boş form'));
$uygulama->rota('POST', '/yazilar', 'kaydet', function (Istek $i, NesneKabi $k) use ($uygulama) {
    $k->al(Olaylar::class)->yay('yazi.kaydedildi');
    $k->al(Kuyruk::class)->ekle('kucukResim', fn() => null);
    $uygulama->yanittanSonra('istatistik', fn() => null);
    return new Yanit(201, 'kaydedildi');
}, [$oturum, $dogrulama]);

$olaylar = $kap->al(Olaylar::class);
$olaylar->dinle('yazi.kaydedildi', 'onbellek', fn() => null);
$olaylar->dinle('yazi.kaydedildi', 'bildirim', fn() => null, kuyrukta: true);
$olaylar->dinle('yazi.olusturuldu', 'dizin', fn() => null);

$istekler = [
    [new Istek('GET', '/yazilar/yeni'), ['form']],
    [new Istek('POST', '/yazilar', ['baslik' => 'Merhaba']), ['oturum', 'dogrulama', 'kaydet']],
    [new Istek('POST', '/yazilar', ['baslik' => ''], true), ['dogrulama', 'kaydet']],
    [new Istek('POST', '/yazilar', ['baslik' => 'Merhaba'], true),
        ['kaydet', 'onbellek', 'bildirim', 'kucukResim', 'istatistik', 'dizin']],
];

$sayac = ['koştu' => 0, 'atlandı' => 0, 'ertelendi' => 0, 'bağlanmadı' => 0];
foreach ($istekler as [$istek, $beklenen]) {
    $yanit = $uygulama->isle($istek);
    printf("%s %s -> %d %s\n", $istek->yontem, $istek->yol, $yanit->durum, $yanit->govde);
    foreach ($beklenen as $parca) {
        $sinif = sinifla($parca);
        $sayac[$sinif]++;
        printf("  %-11s %s\n", $parca, $sinif);
    }
    if ($sonra = $uygulama->bitir()) echo "  yanıttan sonra: ", implode(', ', $sonra), "\n";
}
echo "işçi: ", implode(', ', $kap->al(Kuyruk::class)->isle()), "\n";
foreach ($sayac as $sinif => $n) echo "$sinif $n\n";
```

```
GET /yazilar/yeni -> 200 yazı yeni
  form        bağlanmadı
POST /yazilar -> 302 /giris
  oturum      koştu
  dogrulama   atlandı
  kaydet      atlandı
POST /yazilar -> 422 baslik gerekli
  dogrulama   koştu
  kaydet      atlandı
POST /yazilar -> 201 kaydedildi
  kaydet      koştu
  onbellek    koştu
  bildirim    ertelendi
  kucukResim  ertelendi
  istatistik  ertelendi
  dizin       bağlanmadı
  yanıttan sonra: istatistik
işçi: bildirim, kucukResim
koştu 4
atlandı 3
ertelendi 3
bağlanmadı 2
```

**ÇT4.** On iki beklenen parçadan **4'ü koştu, 3'ü atlandı, 3'ü ertelendi, 2'si bağlanmadı.** Bu dağılım kursun bağlayıcı sayısıdır ve bu çıktıdan çıkar. Sonraki derslerin hiçbiri onu değiştirmez; her ders kendi kesitini ölçer ve kapanış dersi iki ayrı sayıyı ayrı ayrı yazar: çekirdeğin on iki parçası ve yirmi bir dersin kendi sınıfları.

Çıktı üç sınıfı durum koduyla ele verir, dördüncüsünü vermez. İkinci istekte `oturum` koştu ve 302 verdi; arkasındaki `dogrulama` ile `kaydet` yoldaydı ama çağrılmadı, ikisi de **atlandı**. Üçüncü istekte `dogrulama` koştu ve 422 verdi, `kaydet` yine atlandı. Atlanan parça 302, 422 gibi bir kodla kendini ele verir. Dördüncü istekte `bildirim` ve `kucukResim` işçide koştu, `istatistik` yanıt gönderildikten sonra koştu; üçü de 201'in içinde yoktur, yanıt onları beklemedi. Ertelenen parça bir işçide görünür.

**Bağlanmadı en tehlikeli sınıftır**, çünkü hiçbir yerde görünmez. Birinci istek `/yazilar/yeni` adresine gitti ve `form` hiç çağrılmadı: yanıt 200 ve gövdesi `yazı yeni`. Rota tablosunda `form` kayıtlıydı ama `/yazilar/{id}` kalıbı önce kaydedildiği için istek ona uydu. Dördüncü istekte `dizin` dinleyicisi `yazi.olusturuldu` adına kaydedilmişti, eylem `yazi.kaydedildi` yaydı: dinleyici çağrılmadı, yanıt 201. İki durumda da yanıt başarılıdır ve sorun yalnız `Gunluk`'ta görünür.

Bu dersin ölçü sınıfı **koştu**'dur. Dördüncü istekte `kaydet` eylemi ve `onbellek` dinleyicisi yanıt gönderilmeden çağrıldı: çerçevenin söz verdiği akış budur.

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

Sınıflama, geliştiricinin beklediği parçanın adına dayanır. Bir parça gerçekten koşmuş olsa bile, beklenen ad kayıtlı adla uyuşmazsa sınıf değişir mi? Aynı istekte iki ad sorulur: kayıtlı `kaydet` ve bir harf farklı yazılmış `kaydeder`.

```php
<?php
// adlandirma.php — beklenen parçayı geliştirici adlandırır
require 'cerceve.php';

$uygulama = new Uygulama(new NesneKabi());
$uygulama->rota('POST', '/yazilar', 'kaydet', fn() => new Yanit(201, 'kaydedildi'));

$yanit = $uygulama->isle(new Istek('POST', '/yazilar'));
echo $yanit->durum, "\n";
foreach (['kaydet', 'kaydeder'] as $parca) {
    printf("%-9s %s\n", $parca, sinifla($parca));
}
```

```
201
kaydet    koştu
kaydeder  bağlanmadı
```

**ÇT5.** Aynı istek, aynı yanıt, tek bir çağrı: `kaydet` **koştu**, `kaydeder` **bağlanmadı**. Eylem iki ad için de aynı kez çağrıldı, ama `Gunluk` yalnız kayıtlı adı bildi. Demek ki bağlanmadı her zaman bir eşleşme hatası değildir; beklenen ad yanlış yazılmış da olabilir. Sınıflama çerçevenin kayıtlarını değil, geliştiricinin beklentisini sorguladığı için ölçümün güvenilirliği adlandırmanın doğruluğuna bağlıdır.

## Özet

- Çerçevede denetim ters çevrilir: yazılan parça kaydedilir ve onu çerçeve çağırır, çağıran artık yazan taraf değildir.
- `sinifla()` bir parçayı sırayla koştu, ertelendi, atlandı ve bağlanmadı olarak sınıflar; beklenen parçayı geliştirici adlandırır.
- Çekirdek dört istek ve on iki beklenen parçada 4 koştu, 3 atlandı, 3 ertelendi ve 2 bağlanmadı verdi; bu dağılım kursun bağlayıcı sayısıdır.
- Atlanan parça bir durum koduyla, ertelenen parça işçide görünür; bağlanmayan parça yanıt 200 ya da 201 iken hiçbir yerde görünmez.
- Bu dersin ölçü sınıfı **koştu**'dur: dördüncü isteğin `kaydet` eylemi ve `onbellek` dinleyicisi yanıttan önce çağrıldı.

## Sonraki Adım

Dördüncü istekte üç parça yanıtın içinde yoktu: `bildirim` ve `kucukResim` işçide, `istatistik` ise yanıt gönderildikten sonra koştu. Sonraki ders, İstek–Yanıt Yaşam Döngüsü, tek bir soruyu soruyor: yanıt istemciye gittikten sonra çerçeve hâlâ ne koşturuyor ve bunun yanıtın gövdesinde bir izi kalıyor mu?
