PHP 原生不支援事件,需自行實作。本文展示一個 Event 類別,結合觀察者模式與 debug_backtrace() 實現「只有直接持有者才能觸發事件」的存取控制。
class Event {
private $callbacks = [];
private $holder;
public function __construct() {
$bt = debug_backtrace();
// 建構子呼叫者為持有者($bt[1]['object'])
$this->holder = (count($bt) >= 2) ? $bt[1]['object'] : null;
}
// 註冊監聽器:支援閉包或 [物件, 方法名]
public function attach() {
$args = func_get_args();
if (count($args) === 1 && is_callable($args[0])) {
$this->callbacks[] = $args[0];
} elseif (count($args) === 2 && is_object($args[0]) && is_string($args[1])) {
$this->callbacks[] = [$args[0], $args[1]];
}
}
// 觸發事件:檢查呼叫者是否為持有者
public function notify() {
$bt = debug_backtrace();
$caller = (count($bt) >= 2) ? $bt[count($bt)-1]['object'] : null;
if ($this->holder && $caller !== $this->holder) {
throw new Exception('Notify can only be called in holder');
}
$args = func_get_args();
foreach ($this->callbacks as $cb) {
call_user_func_array($cb, $args);
}
}
}
new Event() 通常在持有者類別的建構子或屬性初始化中呼叫,此時 debug_backtrace()[1]['object'] 即為持有者實例。notify() 取得調用堆疊最底層的 $bt[count($bt)-1]['object'](即最初呼叫者),比對是否為持有者。class Order {
public $onShipped;
public function __construct() {
$this->onShipped = new Event();
}
public function ship() {
// 業務邏輯...
$this->onShipped->notify($this, 'shipped'); // 只有 Order 實例能呼叫
}
}
class Notifier {
public function onShipped($order, $status) {
echo "通知客戶:訂單 {$order->id} 已{$status}";
}
}
$order = new Order();
$notifier = new Notifier();
$order->onShipped->attach([$notifier, 'onShipped']);
$order->ship(); // 正常觸發
// 惡意嘗試
$hacker = new class {
public function hack(Event $evt) { $evt->notify('hacked'); }
};
$hacker->hack($order->onShipped); // 拋出 Exception: Notify can only be called in holder
| 優點 | 缺點/風險 |
|---|---|
| 純 PHP 實作,無需擴充套件 | debug_backtrace() 效能較差,高頻事件不適用 |
| 強制封裝,防止外部任意觸發 | 堆疊檢查在 PHP 8+ 傳遞行為可能不同,需實測 |
| 支援閉包與物件方法兩種監聽器 | 無內建優先序、一次性監聽、非同步事件等進階功能 |
此 Event 類別巧妙利用 debug_backtrace() 實現存取控制,展示了 PHP 反射能力與設計模式結合的可能性。理解其原理後,建議專案直接採用成熟的事件分發器,獲得標準化介面、效能優化與豐富生態支援。