跳轉到主要內容
若要從發佈的套件提供像 acme_tracking:AC 這樣的字串規則,需要將驗證處理註冊到驗證的 Factory。如此一來,使用端應用程式的表單請求就能以與內建規則相同的寫法使用它。 本頁以 Laravel Framework v13.40.0 的官方實作為依據。內容補充自訂驗證規則的基本範例,說明將其作為套件公開 API 維護時的註冊、翻譯與狀態生命週期。

與規則物件區分使用

官方文件介紹的 ValidationRule 物件也可以在套件中使用。如果「能以字串指定」本身並不是需求,請考慮使用能透過建構子接收具型別參數的物件。 即使同樣名為 validate(),呼叫契約也不同。若將 ValidationRule 的實作類別直接傳給 Factory::extend() 作為類別字串,第 3 個參數的型別與回傳值都不會吻合。

區分註冊、建立與驗證

Validator Facade 的 extend() 是對 Illuminate\Validation\Factory 的呼叫。它與實際驗證輸入的 Illuminate\Validation\Validator 實例是不同的物件。 此圖是字串規則被執行時的流程。因未輸入或 nullable 等而略過的情況會在後文說明。 Factory 會以 validator 這個鍵註冊為 singleton,但標準實作的 make() 每次都會回傳新建立的 Validator。之後才對 Factory 進行的註冊,不會更新已建立之 Validator 的擴充陣列。

註冊套件專屬的規則

作為範例,我們要驗證一個由英文大寫前綴、連字號與 8 位數字組成的虛構追蹤碼。這只是類似 AC-00000001 格式的範例,並非實際物流公司的編號規格。 假設套件命名空間 Acme\Courier\ 透過 Composer 的 PSR-4 對應到 src/,並以一般的非延遲 Service Provider 載入。關於 Provider 的載入,請參閱套件自動探索。

將驗證處理分離成類別

在 src/Validation/TrackingCode.php 準備字串規則用的處理器。此類別不實作 ValidationRule 介面。
回呼會收到 4 個參數:屬性名稱、輸入值、參數陣列,以及正在驗證的 Validator。以 acme_tracking:AC 為例,參數陣列為 ['AC']。規則本身也會檢查值的型別,即使單獨使用也不會發生型別錯誤。 不正確的前綴或參數數量並不是使用者的輸入錯誤,而是被視為規則定義的設定錯誤並拋出例外。當空值導致規則被略過時,這項設定檢查也不會執行,因此也需要使用正常的非空值進行測試。

在 Factory 解析後註冊

在 src/CourierServiceProvider.php 的 boot() 中註冊規則與訊息替換處理。
callAfterResolving() 在 Factory 尚未解析時會等待其建立,若已經解析則會立即註冊。關於此 Hook 本身的細節,請參閱套件的服務解析後 Hook。 類別字串 TrackingCode::class.'@validate' 會在驗證時透過容器解析類別並呼叫方法。即使省略 @validate 只寫類別名稱,預設的方法名稱也是 validate。若有建構子相依,請進行繫結讓容器能夠解析。 此範例沒有以 singleton 註冊處理器,也沒有設置儲存輸入的屬性。驗證時的資料從參數取得,並設計成不讓 Factory 持有的閉包捕獲請求專屬的狀態。

從使用端應用程式指定

bail 會在該屬性的前面規則失敗時停止後續規則。必填性交給 required,格式交給 acme_tracking,以此區分職責。 公開的名稱請統一使用像 acme_tracking 這樣具辨識性的 snake case。對同一種擴充以相同名稱註冊時,後面的處理會覆寫前者。此外,若存在內建的 validateRequired() 等方法,會直接呼叫該方法,因此無法以同名擴充取代內建驗證。

保留使用者的錯誤訊息

一般字串規則的訊息大致依照以下優先順序選擇。大小規則等特殊分支不在此討論範圍內。 Laravel 替換 :attribute 等之後,才會呼叫自訂 replacer。replacer 的參數有 5 個:已選擇的訊息、屬性名稱、規則名稱、參數陣列與 Validator。前面的範例省略了未使用的第 5 個參數。
如果 replacer 總是把整則訊息替換成固定文字,就會忽略使用者透過 messages() 或翻譯所做的覆寫。前面的範例只替換傳入之 $message 中的 :prefix。
若要在應用程式端指定日文的預設文字,請將 acme_tracking 加入既有 lang/ja/validation.php 的回傳陣列。下方範例只是要新增的項目,並不會刪除其他內建訊息。
翻譯檔的準備與語系設定請參閱在地化。若在註冊時執行 __('...') 並存為 fallback,Factory 中會留下當時語系的字串。在會切換語系的應用程式中,請使用產生錯誤時的翻譯。

使用套件命名空間翻譯的情況

在 boot() 中呼叫 loadTranslationsFrom(__DIR__.'/../lang', 'acme-courier') 後,就能以 acme-courier::validation.tracking_code 參照套件的 lang/ja/validation.php 等檔案。 不過,只把翻譯鍵傳給 extend() 的第 3 個參數並不會自動翻譯。 Factory 只會把它當作單純的 fallback 字串保存。將前面 Hook 中的 extend() 與 replacer() 換成以下註冊後,只有在選中 fallback 時才會取得套件的翻譯。
請在套件各語系的 validation.php 中準備 tracking_code 項目。日文文案可以沿用前面的 acme_tracking 項目。 由於這裡是在 replacer 內取得新的翻譯字串,因此也明確地把 :attribute 傳給翻譯。Laravel 的標準替換並不會再次套用到這個回傳值上。若選中的是使用者自訂的訊息,則維持該文案。關於發佈翻譯的覆寫,請參閱套件翻譯的覆寫與更新。

理解 Implicit 與 nullable 的執行條件

extendImplicit() 會註冊一種即使屬性缺漏或為空字串也會成為驗證對象的規則。它並不代表「一定會失敗」或「忽略其他所有略過條件」。 此表比較的是各規則的執行條件。因 bail、屬性排除、stopOnFirstFailure() 等而提前停止處理的情況另當別論。在 HTTP 輸入中,空字串可能會被中介層轉換成 null,因此請分別測試空字串與 null。 本次的格式規則只要結合一般的 extend() 與 required 就已足夠。將已公開的規則改為 Implicit,可能會讓過去未被驗證的輸入失敗,因此應視為相容性變更來處理。

以 Dependent 規則解析陣列中的參照目標

對於以其他欄位名稱作為參數的規則,可以使用 extendDependent()。驗證 items.*.confirmed_tracking 時,參數中的 items.*.tracking 會被轉換成同一列的 items.0.tracking 等。一般的 extend() 不會進行這種替換。 以下是用來確認此機制的一致性規則。若只是單純驗證是否一致,實際的應用程式可以使用內建的 same。請把它當作應用於套件專屬欄位間驗證時的範例。 將其加入從 Hook 收到的 Factory。
註冊為 Dependent 並不保證參照目標存在或已通過驗證。第 4 個參數的 getData() 是驗證對象的資料,而不只是已驗證的資料。請為參照來源與參照目標兩者都定義必要的規則。 此外,即使設為 Dependent 也不會變成 Implicit。對缺漏或空值的必填性需要另外指定。由於實作會對參數進行點號跳脫處理與 * 替換,請避免設計會混入欄位名稱以外任意字串的 API。

在應用程式中測試從註冊到驗證的流程

在會載入套件與 Service Provider 的 Laravel 13 應用程式中建立 tests/Feature/TrackingRuleTest.php。除了類別單元測試之外,也要透過實際的 Factory 驗證註冊與訊息處理。
最後一個測試不修改共用的 Factory,而是建立新的 Factory 來確認註冊內容的複製行為。若也提供命名空間翻譯或 Dependent 的範例,請在包含對應註冊的環境中準備額外的測試。

長期維護時應確認的事項

  • 將規則名稱、參數的意義與數量、必填性、接受的值型別記錄為公開 API。
  • 驗證不正確的參數、缺漏、空字串、null,以及 nullable、sometimes、bail 的組合。
  • 確認使用者的 messages()、屬性顯示名稱、語系切換,以及套件翻譯的覆寫。
  • 以多列資料與巢狀陣列確認 Dependent 規則不會參照到其他列的值。
  • 確認對尚未解析與已解析之 Factory 的註冊,且不要期待註冊會回溯套用到已建立的 Validator。
  • 避免與多個套件發生名稱衝突,且不要將同一個名稱混用於一般、Implicit 與 Dependent 的註冊。
  • 要讓變更反映到長時間執行的 Worker 時,請重新啟動行程,且不要在共用的處理器中殘留輸入或使用者資訊。
關於套件本身的驗證環境,請參閱套件的測試;關於相容性的處理,也請參閱套件的版本相容性管理。

參考的一手資料

最後修改於 2026年11月5日