İçeriğe geç
academia.sh

Ders 05 / 21

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.

Yayın Güncelleme

Bu derste

  • İ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
İçindekiler

Ö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
// 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
// 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
// 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
// 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ı?

İ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