· Admin

Laravel Observer: Model Event'lerini Temiz Tutmanın Yolu

Model'de boot metoduna bir şey ekliyorsunuz, sonra bir şey daha, bir şey daha... Birden fark ediyorsunuz ki User model'inizin boot metodu 80 satır olmuş: mail gönderme, log tutma, cache temizleme, bildirim atma. Model'in asıl işi veritabanı yapısını tanımlamak ama çoktan bir God Object'e dönmüş.

Observer tam bu noktada devreye giriyor. Model event'lerini model'den ayırıp ayrı bir class'ta toplamak.

Observer Ne Yapıyor?

Eloquent model'leri lifecycle boyunca event'ler fırlatır:

Event Ne Zaman?
creating / created Yeni kayıt oluşturulurken / oluştuktan sonra
updating / updated Kayıt güncellenirken / güncellendikten sonra
deleting / deleted Kayıt silinirken / silindikten sonra
saving / saved Create veya update öncesi / sonrası
restoring / restored SoftDelete'den geri getirilirken

Observer, bu event'leri dinleyip ilgili işlemi çalıştıran ayrı bir class. Model temiz kalıyor, yan etkiler Observer'da yaşıyor.

Basit Bir Örnek

Diyelim ki kullanıcı kaydolduğunda hoşgeldin maili atıyorsunuz, güncellendiğinde log tutuyorsunuz:

php artisan make:observer UserObserver --model=User
namespace App\Observers;

use App\Mail\WelcomeMail;
use App\Models\User;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;

class UserObserver
{
    public function created(User $user): void
    {
        Mail::to($user->email)->queue(new WelcomeMail($user));
    }

    public function updated(User $user): void
    {
        // Sadece email değiştiyse logla
        if ($user->isDirty('email')) {
            Log::info("Kullanıcı email değiştirdi", [
                'user_id' => $user->id,
                'old' => $user->getOriginal('email'),
                'new' => $user->email,
            ]);
        }
    }

    public function deleted(User $user): void
    {
        Log::warning("Kullanıcı silindi: {$user->id} ({$user->email})");
    }
}

Birkaç dikkat noktası:

  • Mail::queue kullanıyorum, Mail::send değil. Observer içinde senkron mail göndermek request'i yavaşlatır. Queue'ya at, arka planda gitsin.
  • updated event'inde isDirty('email') kontrolü var. Her update'te değil, sadece email gerçekten değiştiğinde logla. Bu kontrol olmazsa kullanıcı adını güncellediğinde bile gereksiz log kaydı düşer.

Observer'ı Kaydetmek

Laravel 11'de en basit yol, model'in kendisinde ObservedBy attribute'ü kullanmak:

namespace App\Models;

use App\Observers\UserObserver;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use Illuminate\Foundation\Auth\User as Authenticatable;

#[ObservedBy(UserObserver::class)]
class User extends Authenticatable
{
    // ...
}

Alternatif olarak AppServiceProvider'da da kayıt edebilirsiniz:

public function boot(): void
{
    User::observe(UserObserver::class);
}

İkisi de aynı işi yapıyor. ObservedBy attribute'ü daha temiz çünkü model'e baktığınızda Observer'ın varlığını hemen görüyorsunuz.

Gerçek Dünya Örneği: Sipariş Sistemi

Sipariş oluşturulduğunda stok düşsün, iptal edildiğinde geri yüklensin:

namespace App\Observers;

use App\Models\Order;
use App\Notifications\OrderConfirmation;
use Illuminate\Support\Facades\Log;

class OrderObserver
{
    public function created(Order $order): void
    {
        // Stok düş
        foreach ($order->items as $item) {
            $item->product->decrement('stock', $item->quantity);
        }

        // Sipariş onay bildirimi
        $order->user->notify(new OrderConfirmation($order));

        Log::info("Sipariş oluşturuldu", ['order_id' => $order->id]);
    }

    public function updated(Order $order): void
    {
        // Durum "iptal" olduysa stok geri yükle
        if ($order->isDirty('status') && $order->status === 'cancelled') {
            foreach ($order->items as $item) {
                $item->product->increment('stock', $item->quantity);
            }
        }
    }
}

Controller'da sadece şu kalıyor:

public function store(OrderRequest $request)
{
    $order = Order::create($request->validated());
    $order->items()->createMany($request->items);

    return redirect()->route('orders.show', $order);
}

Stok yönetimi, bildirim, loglama — hepsi Observer'da. Controller sadece işini yapıyor: request al, kayıt oluştur, redirect et.

Observer Ne Zaman Kullanılmalı, Ne Zaman Kullanılmamalı

Kullanın:

  • Model lifecycle'ına bağlı yan etkiler (mail, bildirim, log, cache invalidation)
  • Aynı event'e birden fazla model'de ihtiyaç varsa
  • Controller'ı temiz tutmak istiyorsanız

Kullanmayın:

  • Karmaşık iş mantığı için. "Sipariş oluştur → ödeme al → fatura kes → kargo başlat" gibi çok adımlı süreçler Observer'a sığmaz. Bunun için Service class veya Action pattern daha uygun.
  • Observer'dan başka Observer'ı tetikleyecek zincirleme işlemler için. Debug edemezsiniz.
  • saveQuietly() ile baypas etmeniz gereken durumlar çoğaldıysa, Observer yanlış yerde demektir.

Observer İçinde Dikkat Edilecekler

Queue kullanın. Observer içinde yavaş işlemler (mail, API çağrısı, dosya işleme) varsa bunları mutlaka queue'ya atın. Observer model kaydetme sürecinin içinde çalışır — yavaşlatırsanız kullanıcı beklemesin.

Transaction'a dikkat. created event'i transaction commit'lenmeden önce çalışır. Eğer Observer içinde dış servise istek atıyorsanız ve transaction rollback olursa tutarsızlık oluşur. Bu durumda afterCommit özelliğini kullanın:

class OrderObserver
{
    public $afterCommit = true;

    public function created(Order $order): void
    {
        // Bu kod sadece transaction başarıyla commit edildikten sonra çalışır
        $order->user->notify(new OrderConfirmation($order));
    }
}

Test yazın. Observer'lar görünmez çalıştığı için bug'ları da görünmez oluyor. Her Observer metodu için en az bir test yazın:

public function test_stock_decrements_when_order_created(): void
{
    $product = Product::factory()->create(['stock' => 10]);

    Order::factory()
        ->hasItems(1, ['product_id' => $product->id, 'quantity' => 3])
        ->create();

    $this->assertEquals(7, $product->fresh()->stock);
}

Observer, doğru kullanıldığında kodu gerçekten temizliyor. Ama her araç gibi — ne zaman kullanılacağını bilmek, nasıl kullanılacağını bilmekten daha önemli.