---
title: 'Olaylar ve Dinleyiciler'
source: 'https://academia.sh/tr/kurslar/web-uygulama-cercevesi/olaylar-ve-dinleyiciler'
course: 'Web Uygulama Çerçevesi'
language: tr
updated: '2026-10-21T05:03:38+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - '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'
---

# 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ı.

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
<?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
<?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
<?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
<?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
<?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.
