首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

Zend Framework 事件管理器深度解析(二):共享管理器、監聽器聚合與短路機制

     編輯:PHP 相關知識

核心概念回顧

  • Event(事件):具名動作,包含 `name`、`target`(觸發者)、`params`(參數)。
  • Listener(監聽器):可調用體(Closure/數組/實現 `__invoke` 的對象),響應事件。
  • EventManager(事件管理器):聚合監聽器、觸發事件、管理優先級、提供短路能力。
  • SharedEventManager(共享管理器):跨實例聚合監聽器,通過 `identifier` 模式匹配目標對象。

一、 SharedEventManager:跨對象監聽

場景:多個 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\*', ...)` 通配符匹配,實現「一處配置,全局生效」。

二、 ListenerAggregateInterface:監聽器聚合

將相關監聽器封裝為單一類,支持有狀態、批量掛載/卸載。

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(); // 直接返回響應
    }
});

四、 ResponseCollection:結果集操作

$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
);

六、 優先級控制執行順序

  • 數值越大越早執行(默認 1)。
  • 框架內部監聽器常用負數(-1000 等),業務層用正數即可優先。
  • 同優先級按 `attach` 順序執行。

七、 最佳實踐總結

  1. 領域事件命名規範: `entity.action.phase`,如 `order.created.pre`、`order.created.post`。
  2. 監聽器單一職責: 一個監聽器只做一件事(日誌、緩存、通知、審計),通過優先級編排順序。
  3. 共享管理器用於橫切關注點: 日誌、權限、事務邊界、指標收集。
  4. 聚合類用於業務模塊: `UserEvents`、`OrderEvents` 封裝相關監聽器,模塊啟動時 `attachAggregate`。
  5. 短路用於性能優化: 緩存、熔斷、降級,避免無效計算。
  6. 自定義事件對象傳遞豐富上下文: 避免參數數組膨脹,類型安全。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved