PHP 核心未內建事件/觀察者機制,但透過 debug_backtrace() 可實現「只有直接持有事件對象的呼叫者才能觸發」的限制,構建更安全的事件系統。本文解析一個完整的 Event 類別實作。
[$object, 'method'] 陣列兩種回呼形式。holder(直接持有者),notify() 時檢查呼叫堆疊底部是否為該 holder,否則拋出異常。notify() 將所有參數原樣傳給每個監聽器。<?php
class Event {
private $callbacks = [];
private $holder;
public function __construct() {
$bt = debug_backtrace();
$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();
// 檢查呼叫者是否為直接持有者
if ($this->holder && (count($bt) < 2 || end($bt)['object'] !== $this->holder)) {
throw new Exception('Notify can only be called by holder');
}
$args = func_get_args();
foreach ($this->callbacks as $cb) {
call_user_func_array($cb, $args);
}
}
}
?>
class Order {
public $onShipped;
public function __construct() {
$this->onShipped = new Event();
}
public function ship() {
// 業務邏輯...
$this->onShipped->notify($this, 'shipped'); // 只有 Order 實例能觸發
}
}
$order = new Order();
$order->onShipped->attach(function($order, $status) {
echo "訂單 {$order->id} 已{$status},發送通知郵件。";
});
$order->ship(); // 正常觸發
// 外部嘗試觸發會拋出異常
// $order->onShipped->notify(); // Exception: Notify can only be called by holder
| 優點 | 缺點 |
|---|---|
| 強制封裝觸發權限,符合「事件屬於物件」語意 | debug_backtrace 效能較低,高頻觸發場景不適用 |
| 無需額外擴展,純 PHP 實作 | 堆疊檢查在某些 PHP 版本/配置下可能不可靠 |
現代專案建議使用 Symfony EventDispatcher、Laravel Events 或 PSR-14 相容庫,功能更完善且效能更優。