Laravel 模型事件與事件監聽系統

Observer(模型觀察者)

📌 目的

Observer 是 Laravel 提供的一種設計模式,用來監聽 Eloquent 模型的生命週期事件(如 creatingcreatedupdatingupdateddeletingdeletedsavingretrieved 等),並在這些事件發生時執行對應邏輯。

⚙️ 原理

  • Observer 是綁定於某個 Eloquent 模型的類別。
  • Observer 中的方法名稱對應模型的事件名稱,例如:
    • created(User $user):模型資料建立後觸發
    • updated(User $user):模型資料更新後觸發
  • 這些事件會自動呼叫 Observer 中對應的方法。

✅ 使用範例

# 建立 Observer 類別
php artisan make:observer UserObserver --model=User
// app/Observers/UserObserver.php
use App\Models\User;
use Illuminate\Support\Facades\Log;

class UserObserver
{
    public function created(User $user)
    {
        Log::info('新使用者已建立:' . $user->name);
    }
}
// AppServiceProvider 或專屬服務提供者中註冊
use App\Models\User;
use App\Observers\UserObserver;

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

✅ 建議將觀察者註冊寫在服務提供者中,避免模型中混合過多邏輯。


Listener(事件監聽器)

📌 目的

Listener 是 Laravel 事件系統的一部分,透過手動觸發事件並由對應的 Listener 處理後續邏輯,可達到業務邏輯的解耦與模組化。

⚙️ 原理

  • 使用 event()dispatch() 方法觸發事件。
  • 系統會自動呼叫與該事件對應的所有 Listener 並執行其 handle() 方法。
  • 適合處理與資料庫無直接綁定的業務邏輯(如:發送通知、寄信、寫入第三方)。

✅ 使用範例

# 建立事件與對應的監聽器
php artisan make:event OrderShipped
php artisan make:listener SendShipmentNotification --event=OrderShipped
// 在程式中觸發事件
event(new OrderShipped($order));
// app/Listeners/SendShipmentNotification.php
use App\Events\OrderShipped;
use Illuminate\Support\Facades\Mail;

class SendShipmentNotification
{
    public function handle(OrderShipped $event)
    {
        Mail::to($event->order->user)->send(/* ... */);
    }
}
// EventServiceProvider 中註冊
protected $listen = [
    \App\Events\OrderShipped::class => [
        \App\Listeners\SendShipmentNotification::class,
    ],
];

✅ 事件與 Listener 可用 php artisan event:generate 批次生成。


Observer vs Listener 比較

項目 Observer Listener
觸發時機 綁定 Eloquent 模型事件 手動觸發自定義事件
用途 監聽模型 CRUD 行為、自動邏輯處理 解耦模組邏輯、背景處理
適用設計模式 Observer Pattern Event-Listener Pattern
註冊方式 模型內 observe() EventServiceProvider 中註冊
常見用途 寫入 log、更新統計、通知 寄信、非同步處理、通知、觸發工作任務等

多對多與多型多對多關聯的事件監聽

🧩 策略建議

  • 想要「自動監聽多對多關聯變化」:

    • ✅ 使用 自訂 Pivot 模型 + Observer。
  • 想要「明確控制邏輯觸發點」:

    • ✅ 使用 事件(event) + Listener。
  • 多型關聯(polymorphic)亦可搭配 MorphPivot 模型處理。


使用自訂 Pivot 模型

// 定義自訂 Pivot 模型(需繼承 Illuminate\Database\Eloquent\Relations\Pivot)
use Illuminate\Database\Eloquent\Relations\Pivot;

class RoleUser extends Pivot
{
    protected static function booted()
    {
        static::created(function ($pivot) {
            Log::info("角色已新增至使用者", [
                'user_id' => $pivot->user_id,
                'role_id' => $pivot->role_id,
            ]);
        });
    }
}
// 在模型的 belongsToMany 關聯中指定 using()
public function roles()
{
    return $this->belongsToMany(Role::class)->using(RoleUser::class);
}

使用自訂 MorphPivot 模型(多型多對多)

use Illuminate\Database\Eloquent\Relations\MorphPivot;

class Taggable extends MorphPivot
{
    protected static function booted()
    {
        static::created(function ($pivot) {
            Log::info("標籤已新增", $pivot->toArray());
        });
    }
}
// 在 morphToMany 關聯中使用 using()
public function tags()
{
    return $this->morphToMany(Tag::class, 'taggable')->using(Taggable::class);
}

🔗 官方文件連結