Ders 01 / 21
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.
Yayın Güncelleme
Bu derste
- 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
İçindekiler
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 // 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 // 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 // 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 // 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 // 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
kaydeteylemi veonbellekdinleyicisi 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?
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.