PHP 內建無事件機制,需自行實作 Observer 模式。關鍵需求:監聽者可由任何物件註冊,但事件只能由直接包含該事件物件的持有者觸發。
<?php
class Event {
private $callbacks = array();
private $holder;
function __construct() {
$bt = debug_backtrace();
if (count($bt) < 2) {
$this->holder = null;
return;
}
// 取得實例化 Event 的物件(即持有者)
$this->holder = &$bt[1]['object'];
}
function attach() {
$args = func_get_args();
switch (count($args)) {
case 1:
if (is_callable($args[0])) {
$this->callbacks[] = $args[0];
}
break;
case 2:
if (is_object($args[0]) && is_string($args[1])) {
$this->callbacks[] = array(&$args[0], $args[1]);
}
break;
}
}
function notify() {
$bt = debug_backtrace();
// 驗證呼叫者是否為持有者
if ($this->holder && ((count($bt) >= 2 && $bt[count($bt)-1]['object'] !== $this->holder) || count($bt) < 2)) {
throw new Exception('Notify can only be called in holder');
}
foreach ($this->callbacks as $callback) {
$args = func_get_args();
call_user_func_array($callback, $args);
}
}
}
$bt[1]['object'] 即為實例化 Event 的物件(持有者)[$object, 'methodName'] 陣列class User {
public $onLogin;
function __construct() {
$this->onLogin = new Event();
}
function login($name) {
echo "$name logged in\n";
$this->onLogin->notify($name); // 僅 User 物件可觸發
}
}
$user = new User();
$user->onLogin->attach(function($name) { echo "Send welcome email to $name\n"; });
$user->onLogin->attach($user, 'sendNotification'); // 假設 User 有 sendNotification 方法
$user->login('Alice'); // 正常觸發
// $user->onLogin->notify('Bob'); // 若在外部呼叫會拋出 Exception