---
title: 'Ara Katmanlar'
source: 'https://academia.sh/tr/kurslar/web-uygulama-cercevesi/ara-katmanlar'
course: 'Web Uygulama Çerçevesi'
language: tr
updated: '2026-10-21T05:03:39+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'İsteğin iç içe geçmiş ara katmanlardan geçerek eyleme ulaşmasını ve $sonraki çağrısının anlamını okur'
    - 'Önündeki katman yanıt verdiğinde arkadaki katmanların ve eylemin atlandı sınıfına düştüğünü ölçer'
    - 'Oturumlu istekte bütün katmanların ve eylemin koştu sınıfına düştüğünü karşılaştırır'
    - 'Halka sırasının değişmesiyle aynı isteğin farklı bir katmanda durduğunu ve farklı bir durum koduyla döndüğünü ölçer'
---

# Ara Katmanlar

Kesişen ilgilerin eylemin önüne konan katmanlara taşınması, isteğin katmanlardan geçerek eyleme ulaşması, $sonraki çağrısının yapılması ya da yapılmaması ve halka sırasının sonucu belirlemesi.

Önceki ders eylemin koşması için rotanın eşleşmesinin ve denetleyicinin kurulmasının gerektiğini gösterdi. Ama eylemin önünde, her isteğin geçmesi gereken adımlar da olabilir. Bu denetimler eylemin içine yazılırsa her denetleyicide yinelenir. Çerçeve onları eylemin önüne tek bir yere koyar ve eylem çağrılmadan önce o yerde bir yanıt oluşabilir.

## Kesişen İlgiler ve Ara Katman

Oturumun açık olup olmadığı, girdinin geçerli olup olmadığı, yanıta bir başlık eklenmesi gibi işler, birçok eylemi aynı biçimde ilgilendirir. Bunlara **kesişen ilgi** (cross-cutting concern) denir: hiçbir eyleme özgü değildir, ama hepsinin yolundan geçer. **Ara katman** (middleware) bu işi tek bir yerde yapan bir fonksiyondur. İsteği ve bir `$sonraki` fonksiyonunu alır ve iki şeyden birini yapar: ya isteği `$sonraki`'ye aktararak zincirin devamını çağırır, ya da `$sonraki`'yi çağırmadan kendi yanıtını döndürür.

Katmanlar bir **zincir** oluşturur. Eylem zincirin en içindeki halkadır; her katman bir dış halkadır ve içindekini `$sonraki` olarak alır. İstek en dıştaki halkaya girer, her katman `$sonraki`'yi çağırdıkça bir içeri iner ve eyleme ulaşır. Yanıt aynı yoldan tersine döner: eylemin yanıtı en içteki katmana, oradan bir dıştakine ve en sonunda çağırana ulaşır. Bu yüzden bir katmanın iki bölümü olabilir: `$sonraki` çağrısından önce koşan kısım ve çağrıdan sonra, dönüşte koşan kısım.

`katman.php` bu yapıyı kurar. `zincirle()` katmanları ters sırayla sarar: eylem en içte, en son sarılan katman en dışta kalır. Üç hazır katman vardır. `oturumKatmani()` oturum yoksa 302 ile `/giris` adresine yönlendiren yanıtı kendisi verir. `dogrulamaKatmani()` başlık boşsa 422 verir. `izKatmani()` `$sonraki`'yi çağırır ve **dönüşte** yanıta bir başlık ekler.

Bu yapının bir sonucu, ara katmanın yalnız istekle ilgilenen bir süzgeç olmadığıdır. `$sonraki` çağrısının iki yanı vardır ve her yan ayrı bir iş için uygundur. Çağrıdan önceki kısım isteği inceler ya da değiştirir: oturumu okur, girdiyi doğrular, isteğe bir alan ekler. Çağrıdan sonraki kısım yanıtı inceler ya da değiştirir: başlık ekler, gövdeyi sarar, süre ölçer. Tek bir katman ikisini de yapabilir ve ikisi de aynı fonksiyonun içinde, aynı `$sonraki` çağrısının iki yanında durur.

Katmanın üçüncü bir seçeneği daha vardır ve bu ders onu kasıtlı olarak ölçmez: `$sonraki`'yi birden çok kez çağırmak ya da hiç dönmemek. Çerçeveler bunu genellikle kullanıcıya bırakır. Kural basittir: bir katman ya zinciri sürdürür ya da yanıtı kendisi verir ve hangisini yaptığı zincirin geri kalanı için belirleyicidir.

```php
<?php
// katman.php — iç içe geçmiş ara katmanlar ve zincir
declare(strict_types=1);
require_once 'kostu.php';

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

final class Yanit {
    public array $baslik = [];
    public function __construct(public int $durum, public string $govde = '') {}
}

function oturumKatmani(): array {
    return ['oturum', fn(Istek $i, Closure $sonraki) =>
        $i->oturum ? $sonraki($i) : new Yanit(302, '/giris')];
}

function dogrulamaKatmani(): array {
    return ['dogrulama', fn(Istek $i, Closure $sonraki) =>
        ($i->govde['baslik'] ?? '') !== '' ? $sonraki($i) : new Yanit(422, 'baslik gerekli')];
}

function izKatmani(): array {                  // $sonraki döndükten sonra da çalışır
    return ['iz', function (Istek $i, Closure $sonraki): Yanit {
        $yanit = $sonraki($i);
        $yanit->baslik['x-iz'] = 'gecti';
        return $yanit;
    }];
}

function zincirle(Istek $istek, array $ara, string $parca, Closure $eylem): Yanit {
    Gunluk::yeniIstek();
    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);
    };
    foreach (array_reverse($ara) as [$ad, $katman]) {      // en dıştaki katman en son sarılır
        $hat = function (Istek $i) use ($ad, $katman, $hat): Yanit {
            Gunluk::kostu($ad);
            return $katman($i, $hat);
        };
    }
    return $hat($istek);
}
```

Çekirdek `kostu.php` her derste aynı biçimde 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ı',
    };
}
```

**ÇT15.** `zincirle()` iki iş yapar ve ikisi ayrıdır. İlk `foreach` bütün katman adlarını ve parçayı **yola** koyar: bu istek için zincirde bulunan her halka yoldadır. Sonra her halka çağrıldığında `Gunluk::kostu()` ile kendini koştu olarak bildirir. Fark bilerek böyledir: yol, zincirin ne olduğunu söyler; koştu, zincirin nereye kadar ilerlediğini. Bir halka yoldaysa ama koştu listesinde değilse, ondan önceki bir halka `$sonraki`'yi çağırmamıştır ve karar sırası o parçayı **atlandı** yapar.

## Önündeki Katman Yanıt Verirse

Zincir `[izKatmani, oturumKatmani, dogrulamaKatmani]` sırasındadır ve eylem `kaydet`'tir. `iz` en dıştadır. İki istek işlenir: oturumsuz ve oturumlu. İkisinin gövdesi geçerlidir, yani doğrulamadan geçecek bir başlık taşır; fark yalnız oturumdadır.

```php
<?php
// katmanlar.php — önündeki katman yanıt verirse arkadaki katmanlar atlanır
declare(strict_types=1);
require_once 'katman.php';

$ara = [izKatmani(), oturumKatmani(), dogrulamaKatmani()];
$eylem = fn(Istek $i) => new Yanit(201, 'kaydedildi');
$govde = ['baslik' => 'Merhaba'];

foreach (['oturumsuz' => false, 'oturumlu' => true] as $ad => $oturum) {
    $yanit = zincirle(new Istek('/yazilar', $govde, $oturum), $ara, 'kaydet', $eylem);
    printf("%s -> %d %s %s\n", $ad, $yanit->durum, $yanit->govde, json_encode($yanit->baslik));
    foreach (['iz', 'oturum', 'dogrulama', 'kaydet'] as $parca) {
        printf("  %-10s %s\n", $parca, sinifla($parca));
    }
}
```

```
oturumsuz -> 302 /giris {"x-iz":"gecti"}
  iz         koştu
  oturum     koştu
  dogrulama  atlandı
  kaydet     atlandı
oturumlu -> 201 kaydedildi {"x-iz":"gecti"}
  iz         koştu
  oturum     koştu
  dogrulama  koştu
  kaydet     koştu
```

**ÇT16.** Oturumsuz istekte `oturum` katmanı `$sonraki`'yi çağırmadı ve 302 döndürdü. Onun arkasındaki `dogrulama` katmanı ve `kaydet` eylemi yoldaydı ama çağrılmadı; ikisi **atlandı**. Oturumlu istekte ise `oturum` `$sonraki`'yi çağırdı ve zincirin her halkası çağrıldı: dört parçanın dördü de **koştu** ve yanıt 201 oldu. Aynı zincir, aynı eylem, tek fark isteğin `oturum` alanıdır.

İlk istekte bir ayrıntı daha vardır: yanıt 302 olduğu hâlde başlıkta `x-iz` var. `iz` katmanı oturum katmanının dışındadır; `$sonraki`'yi çağırmış, yanıtı 302 olarak almış ve dönüşte başlığı eklemiştir. Dönüşte koşan kısım, içerideki bir katman erken yanıt verse bile, dışarıdaki katmanlar için koşar. Yani bir katmanın `$sonraki`'den önceki kısmı atlanabilir, ama dışarıdaki bir katmanın dönüş kısmı atlanmaz.

Bu ölçümün bir sınırı vardır. `Gunluk`, bir halkanın çağrılıp çağrılmadığını söyler; çağrılan halkanın içinde ne yaptığını söylemez. `iz` katmanı koştu olarak görünür, ama başlığı dönüşte eklediği günlükte yoktur; onu yalnız yanıtın başlıklarından okuruz. Koştu sınıfı bu yüzden "halka çağrıldı" demektir, "halkanın bütün işi bitti" demek değildir. Dönüşte koşan kısım bir hatayla kesilmiş olsa da halka koştu görünürdü.

Bu fark, ara katman hatalarında önemlidir. Bir katmanın çağrıdan önceki kısmı hata verirse zincir orada kesilir ve arkadaki halkalar yoldadır ama çağrılmamıştır: günlükte atlandı görünürler. Atlanmanın nedeni normal bir erken yanıt da olabilir, bir hata da; ikisini ayıran şey durum kodu ve yanıt gövdesidir, sınıf değil. Bu yüzden atlandı sınıfı bir arıza işareti değildir, bir kontrol akışı işaretidir.

Bu dersin ölçü sınıfı **atlandı**'dır. Atlanan parça bir durum koduyla kendini ele verir: 302 oturumun, 422 doğrulamanın durduğunu söyler. Ama durum kodu yalnız yanıtı veren katmanı gösterir, arkada kaç parçanın atlandığını göstermez; bunu `Gunluk` listeler.

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

Halka sırası sonucu değiştirir mi? İki katmanın sırası, hangisinin yanıtı vereceğini belirler. Oturumsuz **ve** boş başlıklı bir istek iki katmanın da yanıt verme koşulunu sağlar. Aynı istek iki ayrı sırayla ölçülür: önce `oturum`, sonra `dogrulama`; sonra tersi.

```php
<?php
// sira.php — halka sırası hangi katmanın yanıt vereceğini belirler
declare(strict_types=1);
require_once 'katman.php';

$eylem = fn(Istek $i) => new Yanit(201, 'kaydedildi');
$istek = new Istek('/yazilar', ['baslik' => ''], false);     // oturumsuz ve boş başlıklı

foreach (['oturum önce' => [oturumKatmani(), dogrulamaKatmani()],
          'dogrulama önce' => [dogrulamaKatmani(), oturumKatmani()]] as $ad => $ara) {
    $yanit = zincirle($istek, $ara, 'kaydet', $eylem);
    printf("%s: %d %s\n", $ad, $yanit->durum, $yanit->govde);
    foreach (['oturum', 'dogrulama', 'kaydet'] as $parca) {
        printf("  %-10s %s\n", $parca, sinifla($parca));
    }
}
```

```
oturum önce: 302 /giris
  oturum     koştu
  dogrulama  atlandı
  kaydet     atlandı
dogrulama önce: 422 baslik gerekli
  oturum     atlandı
  dogrulama  koştu
  kaydet     atlandı
```

**ÇT17.** İki sırada iki ayrı sonuç çıktı. `oturum` önce olduğunda istek 302 aldı, `oturum` koştu, `dogrulama` ve `kaydet` atlandı. `dogrulama` önce olduğunda istek 422 aldı, `dogrulama` koştu, `oturum` ve `kaydet` atlandı. İstek ve katmanlar aynıdır; yalnız sıra değişti ve durum kodu ile atlanan parça birlikte değişti. Eylem iki sırada da atlandı, çünkü her iki sırada da öndeki katman yanıtı kendisi verdi.

Burada atlanan katmanın hangisi olduğu önemlidir. Sıra `dogrulama` önce olduğunda, oturumsuz bir kullanıcı girdinin ayrıntısını öğrenir: 422 yanıtı, başlığın boş olduğunu söyler. Sıra `oturum` önce olduğunda oturumsuz istek girdiye hiç bakılmadan yönlendirilir. Hangi sıranın doğru olduğu bir güvenlik ve kullanım kararıdır; ölçüm bunu karara bağlamaz, yalnızca sıranın sonucu değiştirdiğini gösterir.

Pratikte bu sıra, hangi katmanın pahalı olduğuna göre de seçilir. Oturum denetimi genellikle ucuzdur ve gerektiğinde isteği daha kapıda eler; doğrulama ise gövdeyi ayrıştırmayı gerektirebilir. Ucuz ve çok eleyen katman öne konursa, atlanan arkadaki katmanlar bu maliyeti hiç ödemez. Bu ölçümde iki katman da ucuzdur ve yalnız hangisinin yanıt verdiği ölçülmüştür; maliyet ayrı bir ölçüm konusudur.

## Özet

- Ara katman, isteği ve `$sonraki` fonksiyonunu alır ve ya `$sonraki`'yi çağırarak zinciri sürdürür, ya da çağırmadan kendi yanıtını döndürür.
- Zincirde eylem en içtedir; istek dıştan içe, yanıt içten dışa ilerler ve bir katmanın `$sonraki`'den sonraki kısmı dönüşte koşar.
- Oturumsuz istekte `oturum` 302 döndürdü, `dogrulama` ve `kaydet` atlandı; dıştaki `iz` katmanı yine de yanıta başlık ekledi. Oturumlu istekte dört parça da koştu.
- `oturum` önce olduğunda aynı istek 302 aldı, `dogrulama` önce olduğunda 422 aldı; halka sırası hangi katmanın yanıt vereceğini belirler.
- Bu dersin ölçü sınıfı **atlandı**'dır: atlanan parça yoldadır ama önündeki bir katman yanıtı erken vermiştir.

## Sonraki Adım

Bu derste katmanları ve eylemi `zincirle()` içinde elle kurduk: hangi parçanın hangi nesneye dayandığını `zincirle()` bilmiyordu. Gerçek bir çerçevede katmanlar, denetleyiciler ve onların bağımlılıkları bir nesne kabında durur ve ad verilerek alınır. Sonraki ders, Nesne Kabı ve Cepheler, kabın bir adı iki kez bağlarsa ne olacağını soruyor: ilk kayıt mı, ikinci kayıt mı çözülür ve ezilen parça isteğe bağlanmış sayılır mı?
