首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 觀察者模式實戰:事件驅動架構的核心實現與解耦藝術

     編輯:PHP 相關知識

PHP 觀察者模式實戰:事件驅動架構的核心實現與解耦藝術

觀察者模式定義對象間一對多依賴,當主題對象狀態改變時,自動通知所有觀察者。它是事件驅動、發布訂閱、插件系統、異步處理的基石。本文提供標準介面定義、核心調度類、多觀察者實現與 Laravel 事件系統對比。

一、核心介面與類定義(符合 SPL 標準)

<?php
// 1. 觀察者介面(PHP 內建 SplObserver)
interface Observer {
    public function update(SplSubject $subject): void;
}

// 2. 主題介面(PHP 內建 SplSubject)
interface Subject {
    public function attach(Observer $observer): void;
    public function detach(Observer $observer): void;
    public function notify(): void;
}

// 3. 具體主題:事件分發器
class EventDispatcher implements Subject {
    /** @var SplObjectStorage 觀察者集合(自動去重、支援對象鍵) */
    private SplObjectStorage $observers;

    public function __construct() {
        $this->observers = new SplObjectStorage();
    }

    public function attach(Observer $observer): void {
        $this->observers->attach($observer);
    }

    public function detach(Observer $observer): void {
        $this->observers->detach($observer);
    }

    public function notify(): void {
        foreach ($this->observers as $observer) {
            $observer->update($this);
        }
    }

    // 業務方法觸發通知
    public function triggerEvent(string $eventName, array $payload = []): void {
        $this->eventName = $eventName;
        $this->payload = $payload;
        $this->notify();
    }

    // 供觀察者讀取
    public function getEventName(): string { return $this->eventName ?? ''; }
    public function getPayload(): array { return $this->payload ?? []; }

    private string $eventName = '';
    private array $payload = [];
}

// 4. 具體觀察者實現
class LogListener implements Observer {
    public function update(SplSubject $subject): void {
        if ($subject instanceof EventDispatcher) {
            error_log("[LOG] Event: {$subject->getEventName()} | Payload: " . json_encode($subject->getPayload()));
        }
    }
}

class EmailNotifier implements Observer {
    public function update(SplSubject $subject): void {
        if ($subject instanceof EventDispatcher) {
            // 發送郵件邏輯...
            echo "[EMAIL] Sending for event: {$subject->getEventName()}\n";
        }
    }
}

class CacheInvalidator implements Observer {
    public function update(SplSubject $subject): void {
        if ($subject instanceof EventDispatcher) {
            // 清理緩存邏輯...
            echo "[CACHE] Invalidating for: {$subject->getEventName()}\n";
        }
    }
}

二、使用示例

<?php
$dispatcher = new EventDispatcher();

// 註冊觀察者
$dispatcher->attach(new LogListener());
$dispatcher->attach(new EmailNotifier());
$dispatcher->attach(new CacheInvalidator());

// 觸發業務事件
$dispatcher->triggerEvent('user.registered', ['user_id' => 123, 'email' => 'test@example.com']);
$dispatcher->triggerEvent('order.placed', ['order_id' => 456, 'amount' => 99.9]);

/* 輸出:
[LOG] Event: user.registered | Payload: {"user_id":123,"email":"test@example.com"}
[EMAIL] Sending for event: user.registered
[CACHE] Invalidating for: user.registered
[LOG] Event: order.placed | Payload: {"order_id":456,"amount":99.9}
[EMAIL] Sending for event: order.placed
[CACHE] Invalidating for: order.placed
*/

三、進階:支援優先級、一次性觀察者、通配符事件

<?php
class AdvancedDispatcher implements Subject {
    /** @var array> 事件名 => [優先級 => 觀察者] */
    private array $listeners = [];
    private array $onceListeners = [];

    public function attach(Observer $observer, string $event = '*', int $priority = 0): void {
        $this->listeners[$event][$priority][] = $observer;
        krsort($this->listeners[$event]); // 高優先級先執行
    }

    public function attachOnce(Observer $observer, string $event = '*', int $priority = 0): void {
        $this->onceListeners[$event][$priority][] = $observer;
        krsort($this->onceListeners[$event]);
    }

    public function detach(Observer $observer, string $event = '*'): void {
        foreach ([$event, '*'] as $e) {
            foreach ($this->listeners[$e] ?? [] as $p => $obs) {
                $this->listeners[$e][$p] = array_filter($obs, fn($o) => $o !== $observer);
            }
        }
    }

    public function notify(string $event, array $payload = []): void {
        $subject = new class($event, $payload) implements SplSubject {
            public function __construct(public string $event, public array $payload) {}
            public function attach(Observer $o) {}
            public function detach(Observer $o) {}
            public function notify() {}
        };

        // 1. 精確匹配 + 通配符
        foreach ([$event, '*'] as $e) {
            // 2. 一次性觀察者
            if (!empty($this->onceListeners[$e])) {
                foreach ($this->onceListeners[$e] as $obs) {
                    foreach ($obs as $o) $o->update($subject);
                }
                unset($this->onceListeners[$e]);
            }
            // 3. 常規觀察者
            if (!empty($this->listeners[$e])) {
                foreach ($this->listeners[$e] as $obs) {
                    foreach ($obs as $o) $o->update($subject);
                }
            }
        }
    }
}

// 使用
$dispatcher = new AdvancedDispatcher();
$dispatcher->attach(new LogListener(), 'user.*', 10);     // 高優先級,匹配 user. 開頭
$dispatcher->attachOnce(new WelcomeEmail(), 'user.registered'); // 只執行一次
$dispatcher->notify('user.registered', ['id' => 1]);

四、觀察者 vs 發布訂閱 vs 中介者

模式耦合度通信方式典型實現
觀察者主題→觀察者(單向)直接調用SplSubject/SplObserver、事件分發器
發布訂閱完全解耦(中介者)事件總線/消息隊列Laravel Events、Symfony EventDispatcher、Redis Pub/Sub
中介者集中協調統一路由MVC 控制器、命令總線

五、Laravel 事件系統對比(生產級推薦)


// 1. 定義事件類
class UserRegistered {
    use SerializesModels;
    public function __construct(public User $user) {}
}

// 2. 定義監聽器
class SendWelcomeEmail {
    public function handle(UserRegistered $event): void {
        Mail::to($event->user->email)->send(new Welcome($event->user));
    }
}

// 3. 註冊映射 (EventServiceProvider)
protected $listen = [
    UserRegistered::class => [SendWelcomeEmail::class, LogRegistration::class],
];

// 4. 觸發
event(new UserRegistered($user));
// 或異步隊列
UserRegistered::dispatch($user)->onQueue('emails');

優勢:異步隊列支援、事務綁定(afterCommit)、模型序列化、監聽器自動發現、測試斷言(Event::fake())。

六、最佳實踐清單

  1. 使用介面而非具體類:依賴 Observer/Subject 介面,便於測試 Mock
  2. 異步化長耗時觀察者:發送郵件、推送、Webhook 放入隊列(Redis/RabbitMQ),notify 僅分發事件對象
  3. 避免循環通知:觀察者內修改主題狀態再次觸發 notify → 死循環,用標記位或異步隊列打破
  4. 記錄日誌與監控:每次分發記錄事件名、觀察者耗時、異常堆棧,接入 Prometheus/Grafana
  5. 單元測試:Mock 觀察者驗證 update 被調用次數、參數正確性

總結:觀察者模式通過主題-觀察者介面契約實現業務邏輯的橫向解耦。PHP 內建 SplSubject/SplObserver 提供標準骨架,生產環境推薦使用 Laravel Events / Symfony EventDispatcher 等成熟組件,獲得異步、事務、測試、監控等企業級能力。核心思想:**發送者不關心誰處理、如何處理,只負責廣播「發生了什麼」**。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved