Skip to main content
想為套件提供事件驅動的擴充點時,要將事件類別的提供與監聽器的註冊分開設計。若採用即使使用者未將 vendor 內的目錄加入自動偵測也能運作的結構,就能減少安裝、更新與建立快取時的前提條件。 本頁會確認 Laravel Framework v13.36.0 的實作,將事件的基本操作套用到套件的長期維護上。提供者的載入請參閱套件自動偵測,測試環境請參閱套件測試。

明確註冊套件的監聽器

套件預設執行的處理,可以在一般 Illuminate\Support\ServiceProvider 的 boot() 中以 Event::listen() 註冊。Laravel 預設的監聽器自動偵測位置是應用程式的 app/Listeners,並不會自動掃描到套件內的 src/Listeners。 以下以將配送完成事件記錄到日誌的套件為例。事件只是一個持有通知所需值的類別。
監聽器在建構子中接收 logger,並在 handle() 中處理事件。
在提供者中註冊事件與監聽器的類別名稱。
此結構的前提是提供者本身會被載入。請準備 Composer 的 extra.laravel.providers,或由使用者明確註冊。若事件在 HTTP 處理與佇列 worker 中都需要,就不要以 runningInConsole() 限制註冊。
請勿僅因為要註冊監聽器,就讓此提供者實作 DeferrableProvider。若延遲對象的服務未被解析,監聽器也不會被註冊。較重的服務繫結請視需要拆分到另一個延遲提供者。

註冊與實例建立發生在不同時機

Dispatcher::listen() 會將針對一般事件名稱的監聽器加入內部陣列。像上面那樣傳入類別名稱時,此時並不會建立監聽器的實例。 對於同步監聽器,事件觸發後 createClassCallable() 會使用容器的 make() 解析類別。因此可以利用建構子的相依性注入。 此路徑並不會對 handle() 執行容器的 call()。請採用將任意服務注入建構子,並在 handle() 中接收事件的設計。不要將其視為與 Artisan 指令對 handle() 的相依性注入相同的機制。 這裡說明的是同步監聽器。實作 ShouldQueue 時會走佇列專用的另一條路徑,因此關於交易、重試與相依性解析,也請確認事件的佇列處理與佇列。

event:cache 不會停止明確註冊

event:cache 並不是將事件 Dispatcher 中已註冊的所有監聽器整個序列化的指令。 EventCacheCommand::getEvents() 會列舉 Illuminate\Foundation\Support\Providers\EventServiceProvider 及其衍生提供者,並儲存各提供者的自動偵測結果與 listens() 的對應。寫在一般 ServiceProvider::boot() 中的 Event::listen() 不在此收集範圍內。 另一方面,即使在有快取的啟動中,一般提供者的 boot() 仍會執行。未包含在快取中,與有快取時不會被註冊,是兩回事。 也可以在繼承 Foundation EventServiceProvider 的套件中使用 $listen。不過,在確認過的實作中,shouldDiscoverEvents() 的預設值是針對該基底類別本身的判斷。僅僅繼承,並不會讓該套件的監聽器目錄被自動偵測。 此外,該提供者的 $subscribe 與 $observers 是在註冊 getEvents() 結果的迴圈之外另行處理。請勿只看 $listen 的快取,就判斷這些註冊也會被省略。
Event::subscribe(SomeSubscriber::class) 會在註冊時由容器建立訂閱者並呼叫 subscribe()。它與以類別名稱進行的 Event::listen() 並不具有相同的延遲性。請避免在訂閱者的建構子或 subscribe() 中產生外部通訊等副作用。

避免與自動偵測重複註冊

若使用者以 withEvents(discover: [...]) 加入套件的監聽器目錄,而套件的提供者也明確註冊了相同的監聽器,一個事件可能會讓處理執行多次。 Dispatcher::listen() 對一般事件會進行陣列的附加,並不會在整個 Dispatcher 中自動移除重複的類別名稱。Foundation EventServiceProvider 在自身對應中進行的 array_unique(),也不是用來合併其他提供者明確註冊的機制。 請將註冊責任交由其中一方負責。
  • 由套件註冊預設監聽器時,不要求使用者將該目錄加入自動偵測。
  • 由使用者選擇所需監聽器時,套件提供事件與監聽器類別,且不預設註冊相同的監聽器。
  • 從自動偵測轉移到明確註冊時,也要將使用者端的偵測設定與舊的事件快取列為需要更新的對象並加以說明。
業務處理的冪等性也很重要,但不要在保留重複註冊的情況下僅以冪等性掩蓋問題,而應整理註冊路徑。

驗證更新前後的行為

要檢查註冊狀態,請在已安裝套件的驗證用應用程式中執行 event:list。確認在無快取與有快取兩種情況下,目標監聽器都存在。
這是在驗證用應用程式中的步驟。僅憑 event:list 的顯示,無法保證監聽器會完成處理,也無法保證只執行一次。請另外加入實際觸發事件的測試。 以下範例在 Testbench 的基底 TestCase 中註冊 CourierServiceProvider 後,驗證 logger 只被呼叫一次。
Event::fake() 便於確認事件是否被觸發,但通常會停止真正監聽器的執行。此測試要驗證註冊與處理,因此不使用它。與自動偵測的組合以及有快取的啟動,也請在另外的驗證用應用程式、新的應用程式實例中確認。
即使在啟動後執行 event:cache,該程序內已註冊的 Dispatcher 也不會從快取重新建構。有快取時的行為確認,請在新的啟動中進行。若要讓更新後的監聽器反映到長時間運作的 worker,也需要依照一般部署流程重新啟動。

長期維護的檢查清單

  • 將事件類別名稱、公開屬性與監聽器的註冊方針,作為面向使用者的約定寫入文件。
  • 變更使用者所訂閱事件的名稱或 payload 時,依照版本相容性管理說明變更內容。
  • 對於自動偵測或 $listen 有變更的更新,將重新建立 event:cache 納入部署流程。
  • 區分套件的預設監聽器與使用者的自訂監聽器,避免重複註冊相同的預設監聽器。
  • 針對每個支援的 PHP 與 Laravel 版本,持續執行確認監聽器只執行一次的測試。

參考的第一手資料

最後修改於 2026年10月13日