<?php
// 事件基礎類別
class Event extends stdClass {
public $target = null;
public $type = null;
public function __construct($type) {
$this->type = trim($type);
}
public function __toString() { return $this->type; }
}
// 自訂事件類別範例
class MyEvent extends Event {
const AA = 'aaa';
}
abstract class EventDispatcher {
private $_callbacks = []; // 儲存監聽器
// 附加監聽器:支援函式或類別方法
public function attach($event, $method, $class = null) {
$eventStr = (string)$event;
if ($this->has($event, $method, $class)) return true;
if ($class !== null) {
$this->_checkMethod($class, $method);
$this->_callbacks[$eventStr][] = $this->_createMethodListener($eventStr, $class, $method);
} else {
$this->_checkFunction($method);
$this->_callbacks[$eventStr][] = $this->_createFunctionListener($eventStr, $method);
}
return true;
}
// 分發事件
public function dispatch(Event $event) {
$event->target = $this;
$eventStr = (string)$event;
if (isset($this->_callbacks[$eventStr])) {
foreach ($this->_callbacks[$eventStr] as $listener) {
if ($listener['object']) {
// 類別方法
if (is_object($listener['class'])) {
$listener['class']->{$listener['method']}($event);
} else {
call_user_func([$listener['class'], $listener['method']], $event);
}
} else {
// 獨立函式
$listener['function']($event);
}
}
}
}
// 移除監聽器
public function detach($event, $method, $class = null) {
$eventStr = (string)$event;
if (!isset($this->_callbacks[$eventStr])) return true;
if (!$this->has($event, $method, $class)) return true;
$target = $class !== null
? $this->_createMethodListener($eventStr, $class, $method)
: $this->_createFunctionListener($eventStr, $method);
foreach ($this->_callbacks[$eventStr] as $k => $v) {
if ($v === $target) {
unset($this->_callbacks[$eventStr][$k]);
return true;
}
}
return true;
}
// 檢查是否已監聽
public function has($event, $method, $class = null) { /* ... */ }
// 內部輔助方法
private function _checkMethod($class, $method) { /* ... */ }
private function _checkFunction($function) { /* ... */ }
private function _createMethodListener($event, $class, $method) { /* ... */ }
private function _createFunctionListener($event, $function) { /* ... */ }
}
<?php
// 發送者:繼承 EventDispatcher
class Ball extends EventDispatcher {
public function aa() {
$event = new MyEvent(MyEvent::AA);
$this->dispatch($event);
}
}
// 監聽者
class MyTest {
public function test() {
$ball = new Ball();
$ball->attach(MyEvent::AA, 'kk'); // 監聽 aa 事件
$ball->aa(); // 觸發事件
}
public function kk($e) {
print_r($e); // 處理事件
}
}
// 執行
try {
$t = new MyTest();
$t->test();
} catch (Exception $e) {
echo $e->getMessage();
}
| 優點 | 缺點/注意 |
|---|---|
| 鬆耦合:發送者不需知道監聽者細節 | 除錯困難:調用堆疊不直觀 |
| 單一職責:事件邏輯與業務邏輯分離 | 效能開銷:大量事件分發有額外負擔 |
| 擴充性強:輕鬆新增/移除監聽者 | 記憶體洩漏風險:忘記 detach 導致物件無法釋放 |
| 支援同步/非同步擴充 | 循環事件風險:A 觸發 B,B 再觸發 A |
EventDispatcherInterface, ListenerProviderInterface 互通標準新專案建議直接採用成熟框架元件,避免重複造輪子。