場景:多個 Service 類都需要「操作日誌」監聽,不想在每個類構造函數裡手動 `attach`。
use Zend\EventManager\EventManager;
use Zend\EventManager\SharedEventManager;
// 1. 全局共享管理器(通常在 Module.php onBootstrap 注入)
$sharedManager = new SharedEventManager();
$sharedManager->attach('My\Module\Service\*', 'doSomething', function($e) {
$logger = $e->getTarget()->getLogger();
$logger->info(sprintf('%s::doSomething called with %s', get_class($e->getTarget()), json_encode($e->getParams())));
}, 100); // 高優先級優先執行
// 2. 各 Service 構造時注入同一 EventManager 實例(或實現 EventManagerAwareInterface)
class UserService {
private $events;
public function __construct(EventManager $events) {
$this->events = $events;
$this->events->setIdentifiers([__CLASS__, get_class($this)]); // 關鍵:設置識別符
}
public function doSomething($param) {
$this->events->trigger(__FUNCTION__, $this, compact('param'));
}
}
識別符機制: `setIdentifiers([__CLASS__, get_class($this)])` 使該實例同時響應類名、實例類名、父類名等標識。共享管理器通過 `attach('My\Module\Service\*', ...)` 通配符匹配,實現「一處配置,全局生效」。
將相關監聽器封裝為單一類,支持有狀態、批量掛載/卸載。
use Zend\EventManager\EventManagerInterface;
use Zend\EventManager\ListenerAggregateInterface;
use Zend\EventManager\EventInterface;
use Zend\Log\Logger;
class LogEvents implements ListenerAggregateInterface {
private $listeners = [];
private $logger;
public function __construct(Logger $logger) {
$this->logger = $logger;
}
public function attach(EventManagerInterface $events): void {
// 監聽多個事件,綁定實例方法
$this->listeners[] = $events->attach('do', [$this, 'log']);
$this->listeners[] = $events->attach('doSomethingElse', [$this, 'log']);
$this->listeners[] = $events->attach('error', [$this, 'logError']);
}
public function detach(EventManagerInterface $events): void {
foreach ($this->listeners as $index => $listener) {
$events->detach($listener);
unset($this->listeners[$index]);
}
}
public function log(EventInterface $e): void {
$this->logger->info(sprintf('%s: %s', $e->getName(), json_encode($e->getParams())));
}
public function logError(EventInterface $e): void {
$this->logger->err(sprintf('%s failed: %s', $e->getName(), $e->getParam('exception')->getMessage()));
}
}
// 使用
$aggregate = new LogEvents($logger);
$events->attachAggregate($aggregate); // 一次性掛載所有
// $events->detachAggregate($aggregate); // 一次性卸載
優勢: 1) 有狀態監聽器(持有 `$logger`);2) 模塊化管理相關邏輯;3) 生命周期可控(模塊卸載時自動 `detach`)。
| 方法 | 參數 | 返回值 | 用途 |
|---|---|---|---|
trigger($name, $target, $argv) | 字符串名、target、參數數組 | ResponseCollection | 普通觸發,執行所有監聽器 |
triggerUntil(callable $callback, $name, $target, $argv) | 回調函數、名、target、參數 | ResponseCollection | 回調返回 true 即停止後續監聽器 |
triggerEvent(EventInterface $event) | 事件對象 | ResponseCollection | 自定義事件對象(攜帶豐富上下文) |
triggerEventUntil(callable $callback, EventInterface $event) | 回調、事件對象 | ResponseCollection | 自定義事件 + 短路 |
public function getUserProfile($userId)
{
$params = compact('userId');
$results = $this->events->triggerUntil(
function ($result) {
return $result instanceof UserProfile; // 滿足條件即短路
},
__FUNCTION__,
$this,
$params
);
if ($results->stopped()) {
return $results->last(); // 返回緩存命中的對象
}
// 無監聽器響應或均未命中,執行原始查詢
$profile = $this->userRepository->find($userId);
// 觸發「查詢後」事件,允許監聽器更新緩存
$this->events->trigger('getUserProfile.post', $this, ['profile' => $profile]);
return $profile;
}
// 緩存監聽器(優先級高)
$events->attach('getUserProfile', function($e) use ($cache) {
$userId = $e->getParam('userId');
if ($cached = $cache->get("profile:$userId")) {
return $cached; // 返回非 null 觸發短路
}
}, 1000);
$events->attach('dispatch', function($e) {
if ($this->isCorsPreflight($e->getRequest())) {
$e->stopPropagation(true); // 停止傳播,不再執行後續監聽器
return new CorsResponse(); // 直接返回響應
}
});
$results = $events->trigger('do', $this, ['foo' => 'bar']);
$results->first(); // 首個監聽器返回值
$results->last(); // 最後一個(短路時常為有效結果)
$results->contains($value); // 是否包含某值
$results->stopped(); // 是否已短路
// 遍歷(實現 SplStack,後進先出)
foreach ($results as $result) {
// ...
}
use Zend\Mvc\MvcEvent; // 框架內建,含 Application、RouteMatch、Request、Response、ViewModel、Result
class ApiEvent extends MvcEvent {
private $apiVersion;
public function setApiVersion($v) { $this->apiVersion = $v; }
public function getApiVersion() { return $this->apiVersion; }
}
// 觸發
$event = new ApiEvent();
$event->setName('api.request');
$event->setTarget($this);
$event->setApiVersion('v2');
$event->setParam('user', $user);
$results = $events->triggerEventUntil(
function($r) { return $r instanceof ApiResponse; },
$event
);