Lesson 12: 隱形的翻譯官-Middleware 中介層的應用場景
想像今天要進入一個戒備森嚴的皇宮(核心商業邏輯 Controller/Service):
大門警衛:檢查你有沒有通行證(Authentication)。
安檢人員:檢查你有沒有攜帶危險物品(Input Validation/Sanitization)。
禮儀官:看你是哪國人,幫你掛上對應語言的翻譯機(Localization)。
記錄員:記下幾點幾分誰進去了(Logging)。
這四個角色,就是 Middleware。它們不負責「處理皇宮內的政務」(那是 Controller 的事),它們負責進出皇宮的「預處理」與「後處理」。
在技術上實現,Middleware 是一個洋蔥式(Onion Architecture)的結構:
Request --> [ Middleware 1 ] --> [ Middleware 2 ] --> (Controller)
|
Response <-- [ Middleware 1 ] <-- [ Middleware 2 ] <--------+
預處理與後處理的過程
public function handle($request, Closure $next)
{
// 前處理(Before Controller)
$response = $next($request);
// 後處理(After Controller)
return $response;
}
為什麼我們需要它?
Bad Smell Code
// UserController.php
public function update(UpdateUserRequest $request)
{
// 1. 檢查有沒有登入 (重複程式碼)
if (!Auth::check()) {
return response('Unauthorized', 401);
}
// 2. 檢查是不是 JSON 格式 (重複程式碼)
if (!$request->isJson()) {
return response('Invalid Content', 400);
}
// 3. 記錄 Log (重複程式碼)
Log::info('User update profile');
// --- 終於開始做正事 ---
$user = Auth::user();
$user->update($request->validated());
return response()->json($user);
}
Modify
Route::put('/user', [UserController::class , 'update'])
->middleware(['auth', 'json.only', 'log.activity']);
// UserController.php
public function update(UpdateUserRequest $request)
{
// 只剩下純粹的商業邏輯
$user = Auth::user();
$user->update($request->validated());
return response()->json($user);
}
Middleware 的核心概念
六大應用場景
A. 身份驗證與授權 (Authentication & Authorization)
場景:確認 User 是否登入?確認 User 是否有「管理員」權限?
功能:如果是無效的 Token,直接在 Middleware 層擋下並回傳 401 或 403,完全不會觸發 Controller,節省資源。
B. 請求數據清洗 (Data Sanitization/Trimming)
場景:使用者輸入表單時,不小心多按了幾個空白鍵,或者輸入了空的字串。
功能:Laravel 預設就有
TrimStrings和ConvertEmptyStringsToNull。它像一個濾水器,把髒東西濾掉後,才交給 Controller 處理。
C. 跨來源資源共用 (CORS - Cross-Origin Resource Sharing)
場景:你的前端在
localhost:3000,後端在localhost:8000,瀏覽器會擋住請求。功能:Middleware 負責在 Response Header 加上
Access-Control-Allow-Origin: *,這是最典型的「隱形翻譯官」,讓瀏覽器聽得懂後端允許跨域。
D. 流量限制 (Rate Limiting)
場景:防止惡意爬蟲或 DDoS 攻擊,或者 API 收費限制(每分鐘只能 Call 60 次)。
功能:記錄 IP 的請求次數,超過限制直接回傳 HTTP 429 (Too Many Requests)。
E. 語系切換 (Localization)
場景:多語系網站。
功能:Middleware 讀取 Request Header 中的
Accept-Language(例如zh-TW),然後設定系統當下的App::setLocale('zh-TW')。這樣 Controller 裡面只需寫trans('messages.welcome'),就會自動對應到正確語言。
F. 效能監控與日誌 (Logging & Profiling)
場景:想知道每個 API 執行了幾秒?
功能:
Request 進來時,記錄時間點 $startTime。
$next($request)執行後續邏輯。Response 回來時,計算
microtime() - $startTime。如果超過 1 秒,寫入 Log 警告。
觀念釐清(ㄧ):Middleware、Policy、Gate
這三個東西做的事情都很像,我們該如何區分呢?
我會利用:「顆粒度 (Granularity)」 與 「是否需要具體的業務資料 (Context Awareness)」
這兩個大方向來進行判斷
Middleware (大樓警衛 - 宏觀過濾)
職責:站在大門口。
檢查內容:你有沒有識別證?你的 IP 是否在黑名單?你的 API Token 是不是過期了?
特點:警衛不關心你要去找誰,也不關心你要去幾樓,他只管「能不能讓你進大廳」。
是否依賴資料:否,通常只看 HTTP Header 或 Session。
Gate (部門門禁卡 - 權限過濾)
職責:電梯或部門入口。
檢查內容:你是「管理員」嗎?你有「財務部」的權限嗎?
特點:這裡開始區分角色(Role),但通常還不涉及具體的「某一份文件」。
是否依賴資料:通常只依賴 User 這種全域資訊。
Policy (保險箱鑰匙 - 微觀/資料過濾)
職責:辦公桌前的最後一道鎖。
檢查內容:你是這份「企劃書」的作者嗎?這張「訂單」是你們部門負責的嗎?
特點:必須拿到**具體的那個物件(Model)**才能判斷。
是否依賴資料:是,高度依賴具體的 Model instance。
為什麼不把 Policy 邏輯寫在 Middleware?
假設你要做一個功能:「使用者只能修改自己的文章」。
如果寫在 Middleware
要在 Middleware 裡做這件事,會遇到兩個大麻煩:
重複查詢資料庫: Middleware 執行時,Controller 還沒開始跑。為了檢查權限,你必須在 Middleware 裡先
find($id)一次文章。然後進了 Controller,為了執行業務邏輯,你可能又find($id)一次。這是資源浪費。解析路由參數很麻煩: Middleware 要知道現在請求的是哪篇文章,必須去解析 URL (
/posts/101/edit) 抓出101。如果路由規則改了,Middleware 的解析邏輯也要跟著改,耦合度太高。
判斷表格
| 維度 | Middleware (中介層) | Gate (閘門) | Policy (策略) |
| 關注點 | HTTP 請求層級 | 使用者權限/功能層級 | 資料模型 (Model) 層級 |
| 典型場景 | 驗證 Token、CORS、Log、鎖 IP、檢查 Content-Type | 能不能進入後台?能不能看報表?(Yes/No) | 能不能刪除這筆訂單?只能改自己的資料? |
| 依賴對象 | Request Header, Session | User Object | User Object + Target Model |
| 執行時機 | 進入 Controller 之前 | Controller 內或 Blade 內 | Controller 內 (處理具體資料時) |
| 語義範例 | "你是合法的使用者嗎?" | "你有管理員權限嗎?" | "你是這篇文章的作者嗎?" |
觀念釐清(二):Middleware、Request與DTO
會有這個釐清的原因是,上面有提到Middleware有一個職責是:清洗資料。
那我們上一篇文章也剛好說到 DTO 以及 FormRequest,那一樣都是資料處理,這三個又差在哪裡呢?
這其實就是:後端工程師在定義不同工具時,工具個別的職責邊界。
我們可以把這三個角色想像成工廠的流水線:
Middleware (清洗):像是「高壓水柱」,不管傳送帶上是什麼原料,先全部沖洗一遍(去掉頭尾空白、空字串轉 Null)。它不關心 這是蘋果還是橘子,它只管「乾淨」。
Form Request (驗證):像是「品管員」。他拿著檢查表,確認這顆「蘋果」有沒有爛掉(Validation),或者把「蘋果」削皮切塊。他很關心這是什麼業務場景。
DTO (傳輸):像是「真空包裝盒」。它不負責清洗也不負責檢查,它只負責把處理好的水果,變成標準的形狀,讓後面的廚師(Service/Domain)好拿、好用。
A. Middleware:全域的、無腦的物理清潔
Middleware (如 Laravel 的 TrimStrings) 是一個全域規則。
特點:它不知道
email欄位是用來幹嘛的,它只知道「凡是字串,前後不該有空白」。適用場景:通用的 HTTP 協議層級處理。
去除空白 (Trim)
空字串轉 Null
HTML標籤過濾 (StripTags - 如果你的網站全站禁止 HTML)
它的盲點:它無法做「特定欄位」的處理(例如:把電話號碼的 拿掉)。
B. Form Request:業務相關的資料預處理
這是在 Controller 之前的業務層級處理。Laravel 的 Form Request 有一個 prepareForValidation() 方法。
特點:它知道現在是在「註冊」,並且知道
phone欄位需要格式化。適用場景:特定 API 的資料修整。
把
0912-345-678轉成0912345678。把
price: "1,000"的逗號拿掉轉成數字。這裡做的「清洗」,是為了讓後面的 Validation 更順利。
C. DTO (Data Transfer Object):型別安全的資料容器
DTO 通常不應該包含複雜的清洗邏輯,它應該是結果的載體。
特點:它假設「進來的資料已經是乾淨且合法的」。
適用場景:將鬆散的
$request->all()陣列,轉換成有型別提示的物件。$request->input('user_id')(不知是 int 還是 string) 轉變成public int $userId。確保傳給 Service 的參數永遠是固定的結構。

