İçeriğe geç
academia.sh

Ders 15 / 21

Olaylar ve Dinleyiciler

Olay, dinleyici ve olay yayınlayıcı; yan etkilerin denetleyiciden ayrılması; kayıtlı ad ile yayılan ad uyuşmadığında dinleyicinin bağlanmaması, kuyruklu dinleyicinin ertelenmesi, olay adının sınıf adıyla verilmesinin uyuşmazlığı kayıt anında yakalaması.

Yayın Güncelleme

Bu derste

  • Olay, dinleyici ve yayınlayıcıyı ayırır ve yan etkinin denetleyiciden nasıl çıktığını gösterir
  • Kayıtlı ad ile yayılan ad uyuşmadığında dinleyicinin bağlanmadığını ve yanıtın yine de 201 döndüğünü ölçer
  • Kuyruklu ve eşzamanlı dinleyiciyi karşılaştırır, olay adını sınıf adıyla vermenin uyuşmazlığı ne zaman yakaladığını gösterir
İçindekiler

Kuyruklar ve İşler dersinde denetleyici küçük resim işini kuyruğa koydu, ama işin sınıfını adıyla bildi: new KucukResimIsi(...). Yazı kaydedilince arama dizinini güncellemek, bir sayacı artırmak, bir günlük satırı yazmak da gerekirse denetleyici her yeni yan etkiyle uzar. Bu ders şunu sorar: denetleyici olanı duyurur, kimin ilgileneceğini bilmezse, ilgilenenler yine de çağrılır mı?

Olay (event) olmuş bir şeyin adıdır ve taşıdığı veridir: YaziKaydedildi olayı yazının kimliğini taşır. Dinleyici (listener), bir olay adına bağlanmış ve olay yayılınca çağrılan fonksiyondur. Olay yayınlayıcı (dispatcher) adlara bağlı dinleyicileri tutar ve yay() çağrılınca eşleşenleri çağırır. Denetleyici yalnız olayı yayar; dizin güncellemesi denetleyiciden çıkar, dinleyiciye gider. Aradaki bağ gevşektir: denetleyici dinleyicinin var olduğunu bilmez, dinleyici denetleyiciyi bilmez. Bu gevşekliğin bir bedeli vardır ve bu dersin ölçümü onu gösterir: ad uyuşmazsa kimse hata vermez.

US3. Olay adı ile dinleyicinin kayıtlı olduğu ad dizge karşılaştırmasıyla eşleşir; eşleşme tam eşitliktir, önek ya da joker yoktur. Olay yayılırken dinleyici bulunamazsa hata fırlatılmaz: hiç dinleyicisi olmayan bir olay meşrudur. Kuyruklu dinleyici, Kuyruklar ve İşler dersindeki gibi aynı süreçtedir; isle() yayınlayıcıda bekleyenleri koşturur. Dinleyici sırası kayıt sırasıdır; olayı durdurma ve öncelik modellenmez.

Ölçüm Çekirdeği

Kurs boyunca her derste aynı ölçüm kullanılır. kostu.php beklenen bir parçanın bu istekte koşup koşmadığını kaydeder; dosya her derste birebir verilir ve çıktı bloğu yoktur, yalnız diske yazılır. Gunluk üç liste tutar: isteğin yoluna konanlar, istek içinde çağrılanlar ve isteğin dışına bırakılanlar. sinifla() bunlara sırayla bakar. Karar sırası sinifla()’nın kendisidir: koştu, ertelendi, atlandı, bağlanmadı. Parçayı geliştirici adlandırır; ölçülen, geliştiricinin bu istekte koşmasını beklediği parçalardır.

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

Bu ders çekirdeğin dört çağrısını da kullanır ve dinleyici için sıra önemlidir: yayınlayıcı bir olay yayınca o ada kayıtlı dinleyicileri yola koyar, eşzamanlı olanı kostu() ile işaretleyip çağırır, kuyruklu olanı ertele() ile işaretleyip kuyruğa bırakır. Hiçbir dinleyici yola konmazsa, ad hiçbir listede yoktur ve sinifla() bağlanmadı der.

Olay, Yayınlayıcı ve Dinleyici

olaylar.php dilimin tamamıdır. YaziKaydedildi yazı kimliğini taşıyan değiştirilemez bir olaydır. Dizin arama dizininin küçültülmüş biçimidir: yalnız eklenen kimliklerin listesi. Yayinlayici üç iş yapar. dinle() bir olay adına bir dinleyici ekler; yay() o ada kayıtlı dinleyicileri sırayla çağırır ya da kuyruğa koyar; isle() kuyruktakileri koşturur ve kaç tane koşturduğunu döndürür. dinleSinif() ise dinle()’nin bir koruması olan halidir: olay adı olarak verilen sınıf yoksa kayıt anında InvalidArgumentException fırlatır. Neden gerektiğini son bölümde göreceğiz.

YazilarDenetleyicisi::kaydet() yazıyı kaydeder (bu derste kaydetmek kaydet parçasının koşması demektir), olayı yayar ve 201 döndürür. Hangi ad altında yayacağı dışarıdan verilir; bu, bir sonraki ölçümde adın kasıtlı olarak uyuşmamasını sağlar.

<?php
// olaylar.php — olay, yayınlayıcı ve dinleyici
declare(strict_types=1);
require_once 'kostu.php';

final class YaziKaydedildi {
    public function __construct(public readonly int $yaziId) {}
}

final class Dizin {
    public static array $kayitlar = [];
}

final class Yayinlayici {
    private array $dinleyiciler = [];   // olay adı => [[ad, fonksiyon, kuyruklu], ...]
    private array $kuyruk = [];

    public function dinle(string $olay, string $ad, callable $islev, bool $kuyruklu = false): void {
        $this->dinleyiciler[$olay][] = [$ad, $islev, $kuyruklu];
    }

    public function dinleSinif(string $sinif, string $ad, callable $islev, bool $kuyruklu = false): void {
        if (!class_exists($sinif)) {
            throw new InvalidArgumentException("olay sinifi yok: $sinif");
        }
        $this->dinle($sinif, $ad, $islev, $kuyruklu);
    }

    public function yay(string $olay, object $yuk): void {
        foreach ($this->dinleyiciler[$olay] ?? [] as [$ad, $islev, $kuyruklu]) {
            Gunluk::yola($ad);
            if ($kuyruklu) {
                Gunluk::ertele($ad);
                $this->kuyruk[] = [$islev, $yuk];
            } else {
                Gunluk::kostu($ad);
                $islev($yuk);
            }
        }
    }

    public function isle(): int {           // işçi: bekleyen dinleyicileri koşturur
        $sayi = count($this->kuyruk);
        while ($is = array_shift($this->kuyruk)) { [$islev, $yuk] = $is; $islev($yuk); }
        return $sayi;
    }
}

final class YazilarDenetleyicisi {
    public function __construct(private Yayinlayici $olaylar, private string $olayAdi) {}
    public function kaydet(int $yaziId): array {
        Gunluk::yeniIstek();
        Gunluk::yola('kaydet');
        Gunluk::kostu('kaydet');
        $this->olaylar->yay($this->olayAdi, new YaziKaydedildi($yaziId));
        return [201, 'kaydedildi'];
    }
}

function dizineEkle(YaziKaydedildi $olay): void { Dizin::$kayitlar[] = $olay->yaziId; }

Dinleyici Bağlanmadı

İlk ölçüm iki kayıt kurar. İkisinde de dizin dinleyicisi vardır ve denetleyici yazi.kaydedildi adıyla olay yayar. Birincisinde dinleyici yazi.olusturuldu adına kayıtlıdır; ikincisinde yazi.kaydedildi adına.

<?php
// bagla.php — dinleyici kayıtlı adla yayılan ad uyuşmuyor
require_once 'olaylar.php';

function olc(string $kayitAdi, string $yayinAdi): void {
    Dizin::$kayitlar = [];
    $olaylar = new Yayinlayici();
    $olaylar->dinle($kayitAdi, 'dizin', 'dizineEkle');
    $yanit = (new YazilarDenetleyicisi($olaylar, $yayinAdi))->kaydet(7);
    printf("kayıt %s, yayın %s -> %d\n", $kayitAdi, $yayinAdi, $yanit[0]);
    foreach (['kaydet', 'dizin'] as $parca) printf("  %-7s %s\n", $parca, sinifla($parca));
    echo '  dizine eklenen: ', count(Dizin::$kayitlar), "\n";
}

olc('yazi.olusturuldu', 'yazi.kaydedildi');
olc('yazi.kaydedildi', 'yazi.kaydedildi');
kayıt yazi.olusturuldu, yayın yazi.kaydedildi -> 201
  kaydet  koştu
  dizin   bağlanmadı
  dizine eklenen: 0
kayıt yazi.kaydedildi, yayın yazi.kaydedildi -> 201
  kaydet  koştu
  dizin   koştu
  dizine eklenen: 1

Bu dersin gözlemi ilk kayıtta. Yanıt 201, kaydet koştu ve dizin bağlanmadı: dinleyici çalışmadı, dizine hiçbir şey eklenmedi, ama hiçbir hata da çıkmadı. Dinleyici yayınlayıcıya kayıtlıydı ve bir fonksiyon olarak sağlamdı; yalnız yayılan ada bağlı değildi. sinifla() önce kostu, sonra ertelenen, sonra yol listesine baktı ve dinleyiciyi hiçbirinde bulamadı: yayınlayıcı o adda dinleyici bulamadığı için dizin’i yola hiç koymadı. Atlanan ile bağlanmayan burada ayrılır. Atlanan parça yoldaydı ve çağrılmadı; bağlanmayan parça yola hiç girmedi.

İkinci kayıt karşıt ölçümdür. Ad uyuşunca aynı dinleyici koştu ve dizine bir kayıt eklendi. Kod aynıdır; değişen yalnız iki dizgenin eşitliğidir. Bu, gevşek bağın bedelinin doğrudan ölçümüdür: derleyici ya da çalışma zamanı bir dizge eşitsizliğini hata saymaz.

Karşıt: Kuyruklu ve Eşzamanlı Dinleyici

Dinleyicinin yanıtı bekletip bekletmediği ayrı bir sorudur. Kuyruklar ve İşler dersindeki sürücü ayrımı burada dinleyicinin bir işaretine iner: kuyruklu bayrağı. Aynı dinleyici bu kez ad uyuşarak kaydedilir ve iki biçimde ölçülür.

<?php
// kuyruklu.php — aynı dinleyici eşzamanlı ve kuyruklu
require_once 'olaylar.php';

foreach ([false, true] as $kuyruklu) {
    Dizin::$kayitlar = [];
    $olaylar = new Yayinlayici();
    $olaylar->dinle('yazi.kaydedildi', 'dizin', 'dizineEkle', $kuyruklu);
    $yanit = (new YazilarDenetleyicisi($olaylar, 'yazi.kaydedildi'))->kaydet(7);
    echo $kuyruklu ? "kuyruklu dinleyici" : "eşzamanlı dinleyici", " -> {$yanit[0]}\n";
    echo '  dizin ', sinifla('dizin'), ', yanıt anında eklenen: ', count(Dizin::$kayitlar), "\n";
    echo '  işçi koşturdu: ', $olaylar->isle(), ', sonra eklenen: ', count(Dizin::$kayitlar), "\n";
}
eşzamanlı dinleyici -> 201
  dizin koştu, yanıt anında eklenen: 1
  işçi koşturdu: 0, sonra eklenen: 1
kuyruklu dinleyici -> 201
  dizin ertelendi, yanıt anında eklenen: 0
  işçi koşturdu: 1, sonra eklenen: 1

Eşzamanlı dinleyicide dizin koştu, yanıt anında dizinde bir kayıt vardır ve işçinin yapacağı bir şey kalmamıştır: isle() sıfır iş koşturdu. Kuyruklu dinleyicide dizin ertelendi: yanıt anında dizin boştur, isle() bir iş koşturur ve ancak sonra kayıt görünür. Sayılar iki yönden tutarlıdır: iki durumda da sonunda dizinde bir kayıt vardır, fark kaydın ne zaman göründüğündedir.

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

Uyuşmazlığın ilk ölçümdeki gibi sessiz kalması, ad bir dizge olduğu içindir. Olay adı olarak sınıf adı verilirse bu risk kayıt anında yakalanabilir mi? Aşağıdaki ölçüm dinleSinif() ile iki kayıt dener: doğru yazılmış YaziKaydedildi ve bir harfi yanlış YaziKaydedilid. Ardından aynı yanlış adı dizge olarak dinle() ile kaydeder ve doğru adı yayar.

<?php
// sinif.php — olay adı dizge ya da sınıf adı
require_once 'olaylar.php';

foreach (['YaziKaydedildi', 'YaziKaydedilid'] as $ad) {
    $olaylar = new Yayinlayici();
    try {
        $olaylar->dinleSinif($ad, 'dizin', 'dizineEkle');
        echo "$ad: kayıt geçti\n";
    } catch (InvalidArgumentException $hata) {
        echo "$ad: kayıt reddedildi (", $hata->getMessage(), ")\n";
    }
}

Gunluk::yeniIstek();
$olaylar = new Yayinlayici();
$olaylar->dinle('YaziKaydedilid', 'dizin', 'dizineEkle');   // dizge: sessizce kabul edilir
$olaylar->yay('YaziKaydedildi', new YaziKaydedildi(7));
echo 'dizge adıyla kayıt, istekte dizin: ', sinifla('dizin'), "\n";
YaziKaydedildi: kayıt geçti
YaziKaydedilid: kayıt reddedildi (olay sinifi yok: YaziKaydedilid)
dizge adıyla kayıt, istekte dizin: bağlanmadı

Sınıf adıyla kayıt, yanlış yazılan adı kayıt anında reddetti: dinleSinif() sınıfın var olup olmadığına baktı ve YaziKaydedilid yoksa istisna fırlattı. Bu kayıt yanıt döngüsünden önce, uygulama kurulurken olur; geliştirici hatayı ilk çalıştırmada görür, ilk kullanıcı isteğinde değil. Aynı yanlış ad dizge olarak verilince kayıt sessizce kabul edildi ve doğru adla yayılan olayda dizin bağlanmadı: ilk ölçümdeki durumun aynısı.

Bir sınır var. Sınıf adı yazım hatasını yakalar; yanlış ama var olan bir sınıfı seçmeyi yakalamaz. Ayrıca koruma dinleSinif()’in yaptığı denetimden gelir: sınıf adı da bir dizgedir ve onu denetleyen kod yazılmadıysa dizgeden farkı kalmaz. Olay adını dizge yerine sınıf adıyla vermek uyuşmazlığı çalışma anından kayıt anına çeker ve bu kazanç denetimin kurulmuş olmasına bağlıdır.

Özet

  • Bu dersin ölçü sınıfı bağlanmadı: dinleyici yazi.olusturuldu adına kayıtlıydı, olay yazi.kaydedildi adıyla yayıldı, dizin çağrılmadı ve yanıt yine de 201 döndü.
  • Olay yayınlayıcı adlara bağlı dinleyicileri çağırır; denetleyici yan etkilerin sayısını ve adını bilmez, ama bunun bedeli sessiz uyuşmazlıktır.
  • Karşıt: ad uyuşunca dinleyici koştu; kuyruklu işaretlenince ertelendi ve kayıt yalnız isle() sonrasında göründü.
  • Sınırlayıcı ölçümde olay adını sınıf adıyla veren kayıt, yanlış yazılan adı kayıt anında reddetti; dizge adı aynı hatayı sessizce kabul etti.
  • Atlanan yoldaydı ve çağrılmadı; bağlanmayan yola hiç girmedi.

Sonraki Adım

Yazı kaydedilince dizin güncellendi, ama kullanıcıya haber vermek ayrı bir yan etki: bir e-posta mı, uygulama içi bir uyarı mı, bir kısa mesaj mı? Bildirimler dersi tek bir olaya birden çok yolla yanıt verir ve alıcının hangi bildirim kanalını seçtiğini ölçer.

İ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