跳轉到主要內容
要為套件加入定期同步或 Heartbeat,需將提供處理與註冊到排程分開。設計上不應因為安裝套件就增加使用端應用程式的定期處理,而是讓使用者能選擇是否啟用、執行頻率,以及在多台伺服器上的運作方式。 本頁確認 Laravel Framework v13.39.0 的官方原始碼,將任務排程的基本操作套用到套件開發。提供者的載入請參閱套件自動偵測的內部結構,設定的發佈請參閱套件設定的合併與快取。

在 Schedule 解析後註冊

在一般的 Laravel 應用程式中,FoundationServiceProvider 會將 Schedule::class 註冊為 singleton。其建立流程會呼叫 Console Kernel 的 resolveConsoleSchedule(),並套用應用程式的時區與排程器用的快取儲存。 套件應擴充這個共用的 Schedule,而不是自行建立 new Schedule() 就結束。以下提供者假設 Acme\Courier\ 透過 Composer 的 PSR-4 對應到 src/,且套件中有 config/courier.php。
callAfterResolving() 在尚未解析時會等待之後的解析,若已經解析則會取得實例並當場呼叫回呼。如此一來,即使其他提供者先使用了 Schedule,也能完成註冊。詳情請參閱套件的服務解析後掛鉤。 runningInConsole() 並不是判斷特定的 Artisan 指令。此範例將透過 HTTP 使用 Schedule 的用途排除在外。此外,註冊掛鉤的提供者本身不應設為延遲提供者,因為若 boot() 未在需要的時間點之前執行,掛鉤也不會被註冊。
事件的註冊與任務的執行是兩回事。載入這個提供者並不會當場執行 Heartbeat。單純註冊也不會啟動 cron 或 schedule:work。

由使用者明確啟用

套件端的 config/courier.php 預設為停用。
使用端應用程式需發佈或建立設定,並將 schedule.enabled 改為 true。此範例會在註冊時讀取設定值。時區預設為 UTC,若選擇其他時區,也需考慮夏令時間造成的重複執行或漏執行。 由於設定是淺層合併,註冊器中也要為每個鍵指定預設值。如此一來,即使部分覆寫導致 schedule 陣列中的其他鍵消失,仍能以基本設定完成註冊。

防止重複註冊到同一個 Schedule

以下的 src/CourierSchedule.php 會註冊一個只記錄一筆日誌的最小 Heartbeat。透過 WeakMap,確保此註冊器對同一個 Schedule 物件只會加入一次。
由於這個註冊器是 singleton,即使從提供者的掛鉤再次取得,也會共用是否已註冊的判斷。另一方面,仍可註冊到新的 Schedule。請避免使用整個程序層級的靜態「已註冊」旗標,以免連新容器或測試中的 Schedule 也被排除。 Schedule::call()、exec()、job() 會將事件加入陣列。它們並不是會找出同名事件並加以替換或去除重複的 API。
withoutOverlapping() 無法修正重複註冊。若同一個任務被註冊了兩次,當先執行的那次結束並釋放鎖定後,後面的事件也可能執行。onOneServer() 也不是移除重複註冊的功能。上述的 WeakMap 只能防止此註冊器的重複。若使用者在 routes/console.php 中加入相同的任務,應停用套件的自動註冊,讓註冊的責任集中在一處。

事件名稱也與鎖定的相容性有關

name() 是 description() 的別名,但對鎖定名稱的影響會因事件類型而異。以下是預設的 mutex 名稱。 在 CallbackEvent 中,需在 withoutOverlapping() 或 onOneServer() 之前先設定非空的名稱。若未設定名稱,這些方法會擲出例外。 job() 通常會以 Job 類別名稱等作為初始名稱。但若以不同的引數或對象註冊同一個 Job,請為每個對象明確指定名稱。僅變更引數,初始名稱並不會自動變成不同的名稱。
SyncCatalogJob 是套件所提供的 Job 類別範例。Job 的實作方式請參閱 Queue 與 Job。 變更名稱、指令或 cron 運算式時,依事件類型不同,鎖定的識別碼也會改變。在新舊版本程序並存的部署中,即使是相同的處理,也可能取得不同的鎖定。請勿將其視同單純的顯示文字變更,而應將任務名稱作為穩定的公開規格來管理。

分開思考 onOneServer 與 withoutOverlapping

  • onOneServer() 會選出在該執行時刻負責執行的伺服器。經確認,CacheSchedulingMutex 會在事件的 mutex 名稱後加上時與分,並以 1 小時的有效期限保存。這與配合任務結束而釋放的防重疊鎖定不同。
  • withoutOverlapping() 會在前一次執行仍在進行時防止重疊。所指定期限的單位為分鐘,預設為 1,440 分鐘。期限過短時,鎖定會在處理完成前失效。
  • 當 job() 將工作推送到 Queue 時,排程事件的完成並不代表 Job 處理完成。Worker 上的重複控制,需另外在 Job 端設計唯一 Job 或 WithoutOverlapping 中介層等機制。
對啟用 onOneServer() 的使用者,應將官方文件指定的 database、memcached、dynamodb、redis 共用快取配置作為必要條件加以說明。鎖定並不保證業務處理的 exactly-once,處理本身也需要具備重新執行也不會出錯的特性。

重量級相依性在執行時解析

上述 Heartbeat 中的 LoggerInterface,是在事件執行時由 CallbackEvent 使用容器的 call() 解析。這是一種在註冊掛鉤時不輸出日誌、也不建立用戶端的配置。 不在建構子中放置外部連線或業務處理的設計,在套件的 Artisan 指令註冊與相依性解析中也很重要。 此外,call() 與 job() 屬於 CallbackEvent,因此無法使用 runInBackground()。耗時的處理可委派給 Queue,或視需要選擇 command() 的背景執行。

不要搶走整個應用程式的設定

Schedule::useCache() 會變更 Schedule 所持有的 mutex 物件的儲存。這並不是只針對套件事件的設定,也會影響共用同一個 mutex 的事件。請勿在套件的 boot() 中無條件呼叫,而應將其視為由使用端應用程式選擇的設定。 同樣地,套件不應擅自變更 Schedule::alwaysOnOneServer()、排程器的暫停,以及維護模式期間的執行方針。

變更設定後重新建立 Schedule

範例中的 enabled、cron 運算式與時區都是註冊時的設定。對已建立的 Schedule 而言,只變更 config() 的值並不會新增或移除事件,也不會重新讀取設定。WeakMap 的判斷也不是用來重新設定的。 在使用設定快取的部署中,請重建快取,並在讀取新設定的程序中確認 schedule:list。對於長時間運作的排程器,以及正在執行未滿 1 分鐘任務的 schedule:run,也要確認運作方式不會殘留舊程式碼或舊設定。子分鐘任務在部署時的中斷,請參閱任務排程。

將註冊與執行分開測試

以下是使用套件的 Composer 自動載入與 PHPUnit 的 tests/CourierScheduleTest.php 範例。將 mutex 設為測試替身,不使用外部快取或實際的 cron 來確認註冊結果。
此測試是註冊器的單元測試。提供者的自動偵測、設定合併以及與 Console Kernel 的整合,需另外透過 Orchestra Testbench 或驗證用的應用程式確認。 請勿為了讓註冊測試通過,而以 schedule:run 執行使用端應用程式的所有任務。將本體的測試與在受控環境中的排程器整合測試分開,即可避免無關定期處理所造成的副作用。

相關頁面

任務排程

確認執行頻率、cron 的導入、委派給 Queue,以及部署時的運作方式。

套件的服務解析後掛鉤

確認如何套用到尚未解析與已解析的服務,以及重新註冊時的注意事項。

參考的一手資料

最後修改於 2026年11月1日