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 介面。
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 個參數。
若要在應用程式端指定日文的預設文字,請將 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。
getData() 是驗證對象的資料,而不只是已驗證的資料。請為參照來源與參照目標兩者都定義必要的規則。
此外,即使設為 Dependent 也不會變成 Implicit。對缺漏或空值的必填性需要另外指定。由於實作會對參數進行點號跳脫處理與 * 替換,請避免設計會混入欄位名稱以外任意字串的 API。
在應用程式中測試從註冊到驗證的流程
在會載入套件與 Service Provider 的 Laravel 13 應用程式中建立tests/Feature/TrackingRuleTest.php。除了類別單元測試之外,也要透過實際的 Factory 驗證註冊與訊息處理。
長期維護時應確認的事項
- 將規則名稱、參數的意義與數量、必填性、接受的值型別記錄為公開 API。
- 驗證不正確的參數、缺漏、空字串、
null,以及nullable、sometimes、bail的組合。 - 確認使用者的
messages()、屬性顯示名稱、語系切換,以及套件翻譯的覆寫。 - 以多列資料與巢狀陣列確認 Dependent 規則不會參照到其他列的值。
- 確認對尚未解析與已解析之 Factory 的註冊,且不要期待註冊會回溯套用到已建立的 Validator。
- 避免與多個套件發生名稱衝突,且不要將同一個名稱混用於一般、Implicit 與 Dependent 的註冊。
- 要讓變更反映到長時間執行的 Worker 時,請重新啟動行程,且不要在共用的處理器中殘留輸入或使用者資訊。
參考的一手資料
- Laravel 官方文件:自訂規則與 Implicit 規則
- Laravel 官方文件:套件的 Service Provider 與翻譯
- Factory:make、extend、extendImplicit、extendDependent、replacer
- Validator:執行條件、擴充註冊、類別解析
- ValidationRuleParser:字串規則的解析
- FormatsMessages:訊息的優先順序與替換
- ValidationServiceProvider:validator 的 singleton 註冊
- ServiceProvider:callAfterResolving
- 官方測試:從 Factory 轉交擴充與 fallback
- 官方測試:字串、類別、Implicit、Dependent 規則