iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 9
0
Modern Web

RRR撞到不負責之 Laravel + Nuxt.js 踩坑全紀錄系列 第 9

Day 09. Request 驗證可以再簡單一點 (Validation)

Server 對於收到的資料都應該要進行驗證確保 server 的安全以及過濾不必要的錯誤。然而自己寫資料驗證頗為麻煩,從昨天的 controller 範例,也看得出來,光是驗證「使用者 ID」和 「文章 ID」,就寫了一大串的判斷。今天依照下面主題逐步介紹 Laravel 提供的驗證機制,讓驗證易寫、好讀、方便維護。

基本驗證

  1. 基本驗證透過呼叫 Request 的 validate 方法。validate 所帶的參數為 array,其中
    • key: request body 的欄位 (資料欄位)
    • value: 要驗證的各種規則

以下面的程式為例,我們要驗證 request 帶進來的 userIdpostIdtitlecontent 四個欄位。

    function edit(Request $request)
    {
        $validResult = $request->validate([
            "userId" => "required|integer|exists:users,id",
            "postId" => "required|integer|exists:posts,id",
            "title" => "nullable|string",
            "content" => "nullable|string"
        ]);
    }
  1. 驗證錯誤預設會跳轉至首頁,由於是開發 api,通常需要處理驗證錯誤的結果,並回拋 response 給 client,因此要透過 try/catch 取得 ValidationException 提取更多細節:
    function edit(Request $request)
    {
        try {
            $validResult = $request->validate([
                "userId" => "required|integer|exists:users,id",
                "postId" => "required|integer|exists:posts,id",
                "title" => "nullable|string",
                "content" => "nullable|string"
            ]);
        } catch (ValidationException $exception) {
            // 取得 laravel Validator 實例
            $validatorInstance = $exception->validator;
            // 取得錯誤資料
            $errorMessageData = $validationInstance->getMessageBag();
            // 取得驗證錯誤訊息
            $errorMessages = $errorMessageData->getMessages();
        }
    }

最終驗證結果 $errorMessages 會是一個 array,分別記錄各別欄位錯誤 (如下圖)。
https://ithelp.ithome.com.tw/upload/images/20190910/20112580gO1UhMn2jf.png

常用驗證方法

Laravel 驗證方法是由「方法名稱」和「條件值」兩個部分組成並用冒號隔開如: <name>:<condition_values>。以下列出部份個人覺得常用的驗證方法,完整列表可參考官網

  • 欄位是否存在以及是否有值:
    • required: 該欄位的值必須符合下列條件
      • 欄位值不可為 null
      • 欄位值不可為空字串
      • 欄位值不可為空陣列
      • 欄位值如果是檔案,則不可缺少 path 資料
    • nullable: 欄位值可為 null,且值為 null 時,其他驗證方法不會因為 null 而報錯
    • present: request 必須要有該欄位,但可以是空值
    • sometimes: 當 request 有該欄位才會進行驗證
  • 布林:
    • boolean: 欄位值必須為可代表布林的資料包括: truefalse10"1""0"
  • 字串:
    • string: 欄位值必須為字串
    • start_with:foo,bar,...: 該欄位值必須是以條件之一做為開頭
    • end_with:foo,bar,...: 欄位值必須是以條件之一做為結束
    • regex:pattern: 欄位值必須符合自訂的正則表示式,如: 'phone' => 'regex:/^09\d{8}$/i'
  • 數值:
    • integer: 欄位值必須為整數
    • numeric: 欄位值必須為數值
  • 檔案:
    • file: 欄位值必須是一個成功上傳的檔案
    • mimetypes:text/plain,...: 上傳的檔案類型,必須符合條件之一的 MIME 類型,如: 'video' => 'mimetypes:video/avi,video/mpeg,video/quicktime'
    • mimes:foo,bar,...: 上傳的檔案類型,必須符合條件之一所對應的 MIME 類型,如: 'photo' => 'mimes:jpeg,bmp,png'
  • 大小:
    • size:value: 欄位值必須符合條件設定的「整數大小」,各項資料大小定義如下:
      • 字串: 字元數量
      • 數值: 數值本身
      • 陣列: 陣列內的項目 (element) 數量
      • 檔案: 檔案 KB 大小
    • max:value: 欄位值最大「不能超過」條件大小
    • min:value: 欄位值最小「不能不足」條件大小
  • 資料庫:
    • exists:table,column: 在條件設定的「資料表欄位」中,必須存在欄位值,如 'userId' => 'exits:users,id'
    • unique:table,column,except,idColumn: 在條件設定的「資料表欄位」中,不可存有欄位值,如 'email' => 'exits:users,email'
  • 其他:
    • bail: 欄位驗證過程中,只要有一個驗證方法不通過便停止驗證
    • confirmed: request 中必須要有一個「<該欄位名稱>_confirmation」的欄位,同時兩者的值要完全相同,如: 'password' => 'confirmed',則 request 中必須有 password_confirmation,且兩個欄位的值必須相同
    • email: 欄位值必須符合 email 的命名規則

客製化錯誤訊息

不論是語系的關係或是應用上的需求,滿常需要客製化錯誤訊息,客製方法如下。其中,客製訊息的 key 是由 「欄位名稱.驗證方法名稱」 格式命名 (輸出錯誤結果如圖)。

    function edit(Request $request)
    {
        try {
            $rules = [
                "userId" => "required|integer|exists:users,id",
                "postId" => "required|integer|exists:posts,id",
            ];
            $message = [
                // 欄位名稱.驗證方法名稱
                "userId.required" => "使用者 ID 為避填資料",
                "userId.exists" => '使用者 ID 必須存在於資料庫中',
                "postId.integer" => "文章 ID 必須為數值",
                "postId.exists" => "文章 ID 不存在"
            ];
            $validResult = $request->validate($rules, $message);
        } catch (ValidationException $exception) {
            $errorMessage =
                $exception->validator->getMessageBag()->getMessages();
        }
    }

https://ithelp.ithome.com.tw/upload/images/20190910/20112580rcHwiGXCjG.png
如果要修改系統所有預設訊息,可以到 resources/lang/en/validation.php 修改,或是建立需要語系的資料夾以及檔案,如 resources/lang/zh_tw/validation.php

巢狀資料驗證

Laravel 也支援巢狀資料的驗證,跟基本驗證相同,唯一差異就是欄位名稱的部分必須包含所有上層的欄位名稱。例如 request body 如下:

{
    "user": {
        "id": 10,
    },
    "post": {
        "id": 5,
        "title": "Day 09. Request 驗證可以再簡單一點 (Validation)",
        "content": null,
    }
}

則驗證規則寫法為:

    function edit(Request $request)
    {
        try {
            $validResult = $request->validate([
                // 可以解釋為: user 的 id
                "user.id" => "required|integer|exists:users,id",
                "post.id" => "required|integer|exists:posts,id",
                "post.title" => "nullable|string",
                "post.content" => "nullable|string"
            ]);
        } catch (ValidationException $exception) {
            $errorMessage =
                $exception->validator->getMessageBag()->getMessages();
        }
    }

陣列資料驗證

陣列資料也是差不多的寫法,假設我們要更新多篇文章,request body 如:

{
    "userId": 10,
    "posts": [
        {
            "id": 3,
            "title": "IT 鐵人第三天",
            "content": null,
        },
        {
            "id": 8,
            "title": "Day 09. Request 超方便驗證幫手 - Validation",
            "content": null,
        }
        // ...
    ]
}

陣列的驗證寫法和巢狀資料一樣,在於要加一個「*」作為辨識:

    function edit(Request $request)
    {
        try {
            $validResult = $request->validate([
                "userId" => "required|integer|exists:users,id",
                // 可以解釋為: posts 的「每一個」的 id
                "posts.*.id" => "required|integer|exists:posts,id",
                "posts.*.title" => "nullable|string",
                "posts.*.content" => "nullable|string"
            ]);
        } catch (ValidationException $exception) {
            $errorMessage =
                $exception->validator->getMessageBag()->getMessages();
        }
    }

有了 validation 之後讓 request 資料的驗證變得更加簡單也好維護...,等等! 那如果有些 request 的欄位驗證和邏輯都相同,如果有需要修改的時候,一個個 function 都要檢查也太累了,有沒有集中管理的辦法? 沒問題! 在明天 FormRequest 的介紹中我們會進一步說明,並且利用套件讓驗證規則可以同步用於前端開發。


上一篇
Day 08. 瘦,是一種生活 - 減脂後的 Controller
下一篇
Day 10. FormRequest 管理驗證規則的好幫手
系列文
RRR撞到不負責之 Laravel + Nuxt.js 踩坑全紀錄31

尚未有邦友留言

立即登入留言