跳轉到主要內容
有時你希望套件的使用者能從多個控制器回傳相同格式的錯誤 JSON。公開回應 Macro 後,無需要求控制器繼承基底類別,就能新增 response()->acmeFailure(...) 這樣的產生 API。 本頁參考 Laravel 官方文件的預設分支 13.x,以及 2026 年 11 月 8 日確認的 Framework 最新版本 v13.40.0 的官方實作。在 回應 與 Macroable trait 的基礎上,解說將其作為發佈套件的公開 API 維護時需要注意的邊界。

註冊目標是 Factory 而非回應

Response 這個名稱既指請求產生回應的 Facade,也指產生後的 HTTP 回應類別。官方文件中的 Response::macro() 擴充的是 Facade 解析到的 ResponseFactory。 不帶參數的 response() 回傳 Factory。而 response('內文') 或 response(null) 回傳由 make() 產生的回應,因此呼叫目標並不相同。
即使註冊到 Illuminate\Http\Response::macro(),也不會成為 response()->acmeFailure() 的 Macro。請不要在 use 陳述式中混淆 Facade 與同名的回應類別。

公開錯誤 JSON 的產生 API

在 Service Provider 中註冊

在 src/HttpServiceProvider.php 的 boot() 中註冊帶有套件前綴的 Macro。關於服務提供者的載入,請參閱 套件自動探索。 本例的公開契約是包含 error.code、error.message 的 JSON,以及 400 到 599 的 HTTP 狀態碼。這個狀態碼限制並非 Laravel Macro 機制的限制,而是套件作為錯誤專用 API 所規定的。
使用 Macro 之前,這個服務提供者必須已經啟動。不要把註冊放在載入 HTTP 路由的檔案中,也不要放在 runningInConsole() 的否定條件裡。HTTP 功能測試同樣從 CLI 執行。 只負責這項註冊的服務提供者,也不需要做成等待服務解析的延遲提供者。延遲註冊的機制請參閱 延遲服務提供者。

從控制器呼叫

在業務邏輯判定出衝突後,傳入事先決定的錯誤碼與給使用者的訊息。
HTTP 狀態碼為 409,JSON 如下所示。與一般的 json() 相同,Content-Type 為 application/json。
透過 Facade 也可以呼叫同一個 Factory 的 Macro。
Macro 只產生呼叫了該方法的回應。它不會自動將驗證例外或認證錯誤轉換為這種格式。也沒有加入依 Accept 標頭切換 HTML 與 JSON 的處理。

Closure 的 $this 與呼叫路徑

Factory 使用了 Macroable。呼叫實例上未定義的方法時,__call() 會尋找已註冊的 Macro,並將一般 Closure 重新綁定到被呼叫的實例上。 即使註冊程式碼位於 Service Provider 的方法中,執行時的 $this 也是 Factory,而不是 Service Provider。因此範例中的 $this->json() 呼叫的是 Factory 的一般方法。
  • 使用 $this 的 Macro 要用一般的 function (...) { ... } 定義。static function 沒有實例的 $this。
  • 註冊 invokable 物件時,不會像 Closure 那樣重新綁定。所需的相依性應由該物件自己透過建構子等方式接收。
  • Macro 的參數是呼叫端傳入的參數。即使宣告了 Request 等型別,Macroable 也不會從容器自動注入。
Facade 的 Response::acmeFailure() 看起來是靜態呼叫,但內部會轉送到 Factory 實例。而具體類別的 Illuminate\Routing\ResponseFactory::acmeFailure() 是真正的靜態呼叫,__callStatic() 會在沒有實例的情況下綁定 Closure。像本例這樣使用 $this->json() 的 Macro,不支援對具體類別的靜態呼叫。

將名稱衝突視為相容性問題

macro() 只是對以名稱為鍵的靜態陣列賦值。註冊已存在的名稱也不會發出警告。 與其使用 failure 這樣的通用名稱,不如選擇 acmeFailure 這樣能辨識套件的名稱。升級 Laravel 時,也要用 method_exists(ResponseFactory::class, 'acmeFailure') 確認是否與實際方法衝突。 如果看到 hasMacro() 就無條件略過註冊,可能會把其他套件的實作當成自己的 API 來使用。只有在把「應用程式可以透過事先註冊來覆寫」作為規格時,才公開其優先順序以及所需的參數與回傳值。上面的範例採用的方針是每次服務提供者啟動時都註冊套件自己的定義。

區分註冊狀態與回應的生命週期

Macro 註冊表是 Factory 的靜態屬性。僅僅重新建立 Factory 或清除 Facade 已解析的實例,並不會清除這張表。在同一個 PHP 行程中重新建立應用程式的測試裡,註冊狀態也可能殘留。 另一方面,範例中的 Macro 每次呼叫時都由 json() 產生新的 JsonResponse。共享的是產生邏輯,而不是回應的內文或標頭本身。 在長時間執行的 worker 中應遵守的邊界如下。
  1. 註冊時不要用 use (...) 捕捉目前的 Request、認證使用者或租戶。這些值應在呼叫時作為參數傳入。
  2. 不要重複使用註冊時產生的回應。每次都回傳新的回應。
  3. 如果在註冊時將設定作為值捕捉,要在規格中說明該值會在行程內保留。
  4. 更新套件後,重新啟動常駐行程以讓新的註冊程式碼生效。
套件服務的生命週期設計 更詳細地討論了容器的共享狀態與請求層級的狀態。
Response::flushMacros() 會刪除註冊到 Factory 的所有 Macro。其他套件的註冊也會遺失,所以不要在正式環境的請求處理中,或作為共享測試環境的草率清理步驟使用它。衝突與重設的驗證應在隔離的測試行程中進行。

以實際回傳值測試 HTTP 契約

在能夠透過 Composer 載入套件的 Laravel 13 驗證用應用程式中,放置以下 tests/Feature/ResponseMacroTest.php。關於 Tests\TestCase 與 HTTP 測試的基礎,請參閱 HTTP 測試;關於套件單獨的環境建置,請參閱 套件測試。
如果像 Response::shouldReceive('acmeFailure') 這樣模擬產生 API 本身,Macro 的註冊、Closure 的綁定與 JSON 的產生都不會執行。請與呼叫次數的驗證分開,像上面那樣驗證實際的 HTTP 回應。 此外,要將使用者收到的內文、狀態碼與標頭視為相容性的對象。
  • 讓 error.code 保持穩定以供機器判定,並將其與顯示訊息的變更區分開來。
  • 參數名稱會被用作具名參數,因此要像預設值與順序一樣確認變更的影響。
  • 204 這樣沒有內文的回應,不要作為錯誤 JSON 回傳,而應使用 noContent() 等其他 API。
  • 即使允許透過自訂標頭修改 Content-Type 的擴充,也要讓它與內文格式一致。
  • 如果目的是資料序列化或分頁,不要只用 Macro 重新實作,也可以考慮 Eloquent API 資源。

長期維護檢查清單

  • 在 Laravel 13 支援的組合中,服務提供者能夠啟動,並且可以從輔助函式與 Facade 兩種方式呼叫。
  • Macro 名稱不會與其他註冊或 Factory 的實際方法衝突。
  • 測試涵蓋了 JSON 鍵、HTTP 狀態碼、標頭、預設值與具名參數的相容性。
  • 連續呼叫時不共享回應,也不在註冊時保存 Request 或使用者資訊。
  • 重設註冊的測試與其他套件隔離。
  • 更新流程中包含常駐行程的重新啟動。

參考的第一手資料

最後修改於 2026年11月8日