# Lesson 12: 隱形的翻譯官-Middleware 中介層的應用場景

想像今天要進入一個戒備森嚴的皇宮（核心商業邏輯 Controller/Service）：

1. **大門警衛**：檢查你有沒有通行證（Authentication）。
    
2. **安檢人員**：檢查你有沒有攜帶危險物品（Input Validation/Sanitization）。
    
3. **禮儀官**：看你是哪國人，幫你掛上對應語言的翻譯機（Localization）。
    
4. **記錄員**：記下幾點幾分誰進去了（Logging）。
    

這四個角色，就是 Middleware。它們不負責「處理皇宮內的政務」（那是 Controller 的事），它們負責進出皇宮的「預處理」與「後處理」。

在技術上實現，Middleware 是一個洋蔥式(Onion Architecture)的結構：

```plaintext
Request  --> [ Middleware 1 ] --> [ Middleware 2 ] --> (Controller)
                                                            |
Response <-- [ Middleware 1 ] <-- [ Middleware 2 ] <--------+
```

## 預處理與後處理的過程

```php
public function handle($request, Closure $next)
{
    // 前處理（Before Controller）
    
    $response = $next($request);
    
    // 後處理（After Controller）
    
    return $response;
}
```

# 為什麼我們需要它？

## Bad Smell Code

```php
// 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

```php
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`](http://localhost:3000)，後端在 [`localhost:8000`](http://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 執行了幾秒？
    
* **功能**：
    
    1. Request 進來時，記錄時間點 $startTime。
        
    2. `$next($request)` 執行後續邏輯。
        
    3. Response 回來時，計算 `microtime() - $startTime`。
        
    4. 如果超過 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 裡做這件事，會遇到兩個大麻煩：

1. 重複查詢資料庫： Middleware 執行時，Controller 還沒開始跑。為了檢查權限，你必須在 Middleware 裡先 `find($id)` 一次文章。然後進了 Controller，為了執行業務邏輯，你可能又 `find($id)` 一次。這是資源浪費。
    
2. 解析路由參數很麻煩： 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，那一樣都是資料處理，這三個又差在哪裡呢？

這其實就是：後端工程師在定義不同工具時，工具個別的職責邊界。

我們可以把這三個角色想像成**工廠的流水線**：

1. Middleware (清洗)：像是「高壓水柱」，不管傳送帶上是什麼原料，先全部沖洗一遍（去掉頭尾空白、空字串轉 Null）。它不關心 這是蘋果還是橘子，它只管「乾淨」。
    
2. Form Request (驗證)：像是「品管員」。他拿著檢查表，確認這顆「蘋果」有沒有爛掉（Validation），或者把「蘋果」削皮切塊。他很關心這是什麼業務場景。
    
3. 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 的參數永遠是固定的結構。
        

<div data-node-type="callout">
<div data-node-type="callout-emoji">💡</div>
<div data-node-type="callout-text">補充：Laravel如果有用FormRequest來做檢查，可以用validated()來過濾rules中不存在的資料，不用多寫一個DTO。但會有一個情況：假設我們定義了一個SearchRequest，欄位大同小異，但因為某些欄位時有時無，邏輯複雜到不太能用 required_if, required_without, exclude_if 之類的輔助條件，而不得不拆成三個Request;我們就可以嘗試用一個囊括全部的Request，並只做檢查，在利用各自功能的DTO來正規化進到Service的結構內容。</div>
</div>
