「Middleware 就是驗證登入、檢查權限那種東西吧?」
這是很多人對 middleware 最直覺的印象——確實,驗證登入、檢查權限是 middleware 最常見的用途,但 middleware 能做的事情比這廣得多:只要是「在請求進到 controller 之前、或回應送出使用者之前,需要統一處理的邏輯」,都可以放進 middleware。今天用兩支結構完全不同的真實 middleware,看看這個機制的彈性有多大。
先看第一支:一支只做一件事的 middleware,職責是替每個回應加上 Content-Security-Policy(CSP)這個安全性標頭,內容從設定檔讀取。
這支 middleware 的邏輯精簡到幾乎沒有分支:呼叫 $next($request) 拿到 controller 產生的回應,替它加一個標頭,回傳。沒有判斷條件、沒有提前中斷請求,是 middleware 能做到的最小規模。
這種「加一個回應標頭」的需求,天生就適合放進 middleware,而不是散落在每個 controller 裡各自加——理由很直接:CSP 這種安全性標頭應該是「全站一致」的規則,如果讓每個 controller 自己決定要不要加,遲早會有某個 controller 忘記加,變成防護有漏洞的那一個。集中放進 middleware,等於用架構上的方式保證「這條規則不會被漏掉」。
再看第二支,複雜度明顯高一階:它的職責是判斷「這個請求存取的網域,跟這個路由『應該』對應的網域是不是一致」,不一致就重導到正確的網域。
具體情境是這樣:這個系列的素材專案有多個對外網域,同一個路由名稱(例如某支新聞的顯示頁)理論上要用某個特定網域存取。這支 middleware 攔在路由分派之後、controller 執行之前,檢查目前的網域參數跟路由「應該」對應的網域是否一致——不一致就重導,讓使用者最終落在正確的網域上。
這支 middleware 值得注意的地方是:它讀取了路由參數、判斷了資料型別(是不是新聞頁面、頁面本身的 type 是不是新聞類型),這已經不是單純的技術性攔截,而是業務邏輯——「什麼情況下該重導去新聞網域」本身就是一條業務規則,只是這條規則被放在 middleware 這個位置執行,而不是寫進 controller。
看完這兩個例子,可以整理出一個判斷依據:一段邏輯適不適合放進 middleware,關鍵不在於它是「技術性」還是「業務性」,而在於這段邏輯是不是要在多個路由/多個 controller 之間一致套用、而且發生的時機是在「進入 controller 之前」或「回應送出之前」。
加 CSP 標頭符合這個條件——全站每個回應都要加。網域重導邏輯也符合——只要是牽涉到特定路由的請求,進 controller 之前就要先判斷完,controller 本身不需要知道這件事發生過。
反過來說,如果一段業務邏輯只跟某一個特定 controller 的行為有關,跟其他路由完全無關,那就不該硬塞進 middleware——middleware 是全域或群組層級的機制,把只跟單一情境相關的邏輯放進去,會讓這支 middleware 變得難以理解「它到底管什麼」。
用一組對照來看:
✅ 適合放進 middleware
- 全站一致的安全性標頭(CSP)
- 多個路由都要套用的存取控制/重導規則
- 進 controller 前就能判斷完、不需要 controller 知道的邏輯
❌ 不適合硬塞進 middleware
- 只跟單一 controller/單一 action 相關的邏輯
→ 這種邏輯留在 controller 裡,或抽成 controller 自己呼叫的方法,
塞進 middleware 只會讓「這支 middleware 在管什麼」變得難以理解
這個系列的素材專案裡,還有一支 middleware 承擔了比前面兩支都複雜得多的職責——它會依照請求的 session 或參數,動態決定整個網站要用哪一套畫面呈現,牽涉到 Laravel 的 View 尋找機制。這支 middleware 值得獨立一整篇來講,本篇先不展開——明天先講另一個主題,這支 middleware 的完整機制留到後面幾天深入。
回想你的專案裡,有沒有一段邏輯目前散落在多個 controller 裡各自重複實作,但其實符合「全站一致、進 controller 前就能判斷完」這個條件,適合收斂成一支 middleware?
明天換一個主題,看檔案儲存的路徑設計——一個只有十幾行的類別,怎麼讓上傳的檔案路徑從一串難以排查的雜湊值,變成人一眼就能看懂的結構。