# Laravel'de olaylar (events) ve dinleyiciler

> Laravel'in olay sistemiyle yan etkileri ana akıştan nasıl ayırdığımı ve pratikte ne kazandığımı örneklerle anlatıyorum.

- Yayın: 2016-05-01
- Kategori: Framework & Kütüphane
- Etiketler: Laravel
- Okuma süresi: 4 dk okuma
- Kaynak: https://www.muhammetsafak.com.tr/blog/laravel-de-olaylar-events-ve-dinleyiciler/
- Dil: tr-TR
- Yazar: Muhammet Şafak

---
Bir kullanıcı kayıt olduğunda ne yapılması gerekiyor? Hoşgeldin e-postası gönder, kullanıcı aktivitesini kaydet, belki pazarlama servisine bildir. Bu adımların hepsini `UserController@register` metoduna yazmak kolay — ama birkaç ay sonra o metodun içi dolup taşıyor.

Laravel'in event ve listener sistemi, bu yan etkileri kayıt işleminin kendisinden ayırmak için var. Bu yazıda bu sistemi nasıl kullandığımı aktarıyorum.

## Olay nedir?

**Olay**, uygulamada gerçekleşen anlamlı bir şeyi temsil eden basit bir PHP sınıfıdır. `UserRegistered`, `OrderPlaced`, `PasswordChanged` gibi. Olayın kendisi herhangi bir iş yapmaz; yalnızca "bu oldu" der ve ilgili veriyi taşır.

**Listener**, bu olayı karşılayan ve bir işi gerçekleştiren sınıftır. Bir olayın birden fazla listener'ı olabilir.

## Kullanım örneği: kullanıcı kaydı

Önce olay sınıfını oluşturalım:

```bash
php artisan make:event UserRegistered
php artisan make:listener SendWelcomeEmail --event=UserRegistered
php artisan make:listener LogUserActivity  --event=UserRegistered
```

Olay sınıfı, yalnızca taşıyacağı veriyi tutar:

```php
<?php

namespace App\Events;

use App\User;
use Illuminate\Queue\SerializesModels;

class UserRegistered
{
    use SerializesModels;

    public User $user;

    public function __construct(User $user)
    {
        $this->user = $user;
    }
}
```

İlk dinleyici e-posta gönderimi yapıyor:

```php
<?php

namespace App\Listeners;

use App\Events\UserRegistered;

class SendWelcomeEmail
{
    public function handle(UserRegistered $event): void
    {
        \Mail::send('emails.welcome', ['user' => $event->user], function ($m) use ($event) {
            $m->to($event->user->email)
              ->subject('Hoşgeldiniz!');
        });
    }
}
```

İkinci dinleyici aktiviteyi kaydediyor:

```php
<?php

namespace App\Listeners;

use App\Events\UserRegistered;
use App\ActivityLog;

class LogUserActivity
{
    public function handle(UserRegistered $event): void
    {
        ActivityLog::create([
            'user_id' => $event->user->id,
            'action'  => 'registered',
        ]);
    }
}
```

## Olay servis sağlayıcısında bağlamak

`EventServiceProvider` içinde olayı dinleyicileriyle eşleştiriyoruz:

```php
<?php

namespace App\Providers;

use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;

class EventServiceProvider extends ServiceProvider
{
    protected $listen = [
        \App\Events\UserRegistered::class => [
            \App\Listeners\SendWelcomeEmail::class,
            \App\Listeners\LogUserActivity::class,
        ],
    ];
}
```

## Controller'da olayı tetiklemek

Controller artık yalnızca kayıt işini yapıyor; yan etkilerle ilgilenmiyor:

```php
<?php

namespace App\Http\Controllers;

use App\Events\UserRegistered;
use App\User;
use Illuminate\Http\Request;

class RegisterController extends Controller
{
    public function store(Request $request)
    {
        $this->validate($request, [
            'email'    => 'required|email|unique:users',
            'password' => 'required|min:8',
        ]);

        $user = User::create([
            'email'    => $request->email,
            'password' => bcrypt($request->password),
        ]);

        event(new UserRegistered($user));

        return redirect('/')->with('mesaj', 'Kaydınız tamamlandı!');
    }
}
```

`event(new UserRegistered($user))` satırı her şeyi başlatıyor. Controller bunun arkasında ne olduğunu bilmek zorunda değil.

## Bu yapının kazancı nedir?

Yeni bir gereksinim geldiğinde — örneğin kayıt sonrası bir Slack bildirimi göndermek — yalnızca yeni bir dinleyici yazıp `EventServiceProvider`'a ekliyorum. Controller'a ve diğer dinleyicilere dokunmuyorum.

Ters yönde de geçerli: e-posta gönderme mantığını değiştirmem gerekirse yalnızca `SendWelcomeEmail` sınıfını açıyorum. Kayıt akışının geri kalanıyla hiçbir ilgisi yok.

Bir başka pratik kazanç: `ShouldQueue` interface'ini ekleyerek dinleyiciyi queue'ya alabiliyorsunuz, yani e-posta gönderimi arka planda işlenebiliyor. Bunu yapmak için listener'a tek bir şey eklemek yeterli:

```php
use Illuminate\Contracts\Queue\ShouldQueue;

class SendWelcomeEmail implements ShouldQueue
{
    // ...
}
```

Controller değişmiyor. Bu seviyedeki bir ayrıştırma olmadan bu kadar kolay olmazdı.

## Dinleyicileri ayrı ayrı test etmek

Bu yapının test edilebilirlik açısından da bir kazancı var. Her dinleyici bağımsız bir sınıf olduğu için ayrı ayrı test edebiliyorsunuz. Gerçek `UserRegistered` olayını elle oluşturup dinleyiciyi doğrudan çalıştırmak mümkün:

```php
public function test_hosgeldin_epostasi_gonderilir()
{
    Mail::fake();

    $user = User::factory()->create();
    $listener = new SendWelcomeEmail();
    $listener->handle(new UserRegistered($user));

    Mail::assertSent(WelcomeMail::class, function ($mail) use ($user) {
        return $mail->hasTo($user->email);
    });
}
```

Tüm kayıt akışını HTTP üzerinden tetiklemek gerekmeden yalnızca dinleyicinin doğru çalışıp çalışmadığını test ediyorsunuz. Bu, controller içine gömülü kod için mümkün olmayan bir yalıtım.

## Olay isimlendirmesi önemli

Olay sınıfı adları uygulamanın dili haline geliyor. `UserRegistered`, `OrderShipped`, `PaymentFailed` gibi geçmiş zamanlı isimler, "bir şey oldu" anlamını net taşıyor ve kodu okuyan birinin sistemi anlamasını kolaylaştırıyor. `DoSendEmail` ya da `HandleUserRegistration` gibi isimler ise olayı dinleyiciyle karıştırıyor; oku bulanıklaşıyor.

Bu konuya başlangıçta dikkat etmemiştim; birkaç yanlış isimli olay sınıfı yarattıktan sonra farkettim ki isimlendirme kodu anlamak için gerçek bir ipucu. Olayı "ne oldu?" sorusuna cevap verecek şekilde adlandırmak, dinleyicileri de "bu olduğunda ne yapılmalı?" sorusuna göre adlandırmayı kolaylaştırıyor.

## Dikkat ettiğim bir nokta

Olayları aşırı kullanmak takibi güçleştirebilir. Bir iş adımını her olayı ateşleyerek zincirlemeye başlarsanız, bir isteğin hangi akışı izlediğini anlamak için birkaç farklı dosyaya bakmanız gerekiyor. Yan etkileri ayırmak için iyi, birincil akışı dağıtmak için değil — bunu aklımda tutmaya çalışıyorum.

Basit bir kural edindim: bir controller metodu içinde kalıp da "bu işi buraya koyarsam metodun sorumluluğu büyür" diye düşünüyorsam olay sistemi devreye giriyor. Ama aynı akıştaki zorunlu iş adımlarını — örneğin sipariş oluştururken stok düşürme — olaya taşımak mantıklı değil. O adım olmadan iş tamamlanmış sayılamaz; onu dinleyiciye gömmek hem takibi güçleştiriyor hem de hata yönetimini karmaşıklaştırıyor. Olay sistemi, "olursa iyi ama zorunlu değil" ya da "sonradan eklenebilir" türünden yan etkiler için en verimli çalışıyor.
