編輯會員資料時,請求沒有帶 name,驗證卻通過了;另一個表單明明帶了 name: null,又被判為失敗。先不要把兩者都叫做「空欄位」。欄位沒出現在輸入裡、出現但值為 null、出現而值為空字串,是三種不同的資料。
Laravel的 sometimes、present、required 各自回答不同問題。要寫對更新表單,先決定「沒有送來」代表不修改,還是代表這份輸入不完整,再選規則。本文聚焦欄位是否存在;不處理網址格式或模型批量賦值。
沒有送欄位,可以是合理的部分更新
假設會員暱稱原本是 Ada,這次請求只改聯絡電話。name 不在 payload裡,就可能代表保持原值,而不是清空。這類部分更新,可以在該欄位使用 sometimes,讓規則只在欄位出現時執行。
$rules = ['name' => ['sometimes', 'required', 'string']];
這裡的 sometimes 並沒有把 required 取消。它讓整組規則在 key缺席時跳過;key一旦出現,required仍要求它有合適的非空值,string仍檢查型別。因此「沒傳 name」可以通過,「傳 name但值為null」則不能照樣通過。
這項差異很適合用在讀者可照做的測試上:分別送 {} 與 {"name": null},不要只在表單裡把文字刪光再重送。前端表單、JSON請求和你的序列化程式可能產生不同 payload;光看欄位畫面是空白,不能知道 key到底有沒有被送出。
present要求有key,並不要求有非空文字
如果輸入契約規定每次都必須帶 name,但允許用null表示清空,則需要處理「存在」與「值」兩部分。
$rules = ['name' => ['present', 'nullable', 'string']];
present要求輸入中有這個欄位;nullable讓null可接受;有非null的有效輸入時,再按其他規則檢查。這和 sometimes|required|string 的任務不同:前者不能少key,後者可以少key,但送來時不能空。
也不要把 present單獨當成「必填文字」。它只處理欄位是否存在,不能代替 required,更不能代替長度、格式或授權檢查。若表單每次都應送一個非空文字,使用 required|string通常比較直接。真正的規則應由資料契約決定,不能為了讓紅字消失而挑較寬鬆的一組。
四種輸入,分別跑三組規則
下面可以在自有Laravel測試專案的Tinker執行。測試只跑Validator,不修改資料庫。先準備四份資料,再用相同欄位依序驗證,避免每次手動改字串時把案例搞混。
use Illuminate\Support\Facades\Validator;
$inputs = [
'missing' => [],
'null' => ['name' => null],
'empty' => ['name' => ''],
'value' => ['name' => 'Ada'],
];
$rules = [
'sometimes_required' => ['sometimes', 'required', 'string'],
'present_nullable' => ['present', 'nullable', 'string'],
'required' => ['required', 'string'],
];
foreach ($inputs as $case => $data) {
foreach ($rules as $label => $rule) {
$v = Validator::make($data, ['name' => $rule]);
dump($case, $label, $v->passes(), $v->failed());
}
}
本文在PHP8.4.2與Laravel13.34.0的直接Validator案例中,missing只通過 sometimes_required;null與空字串只通過 present_nullable;Ada則通過全部三組。missing在 sometimes_required通過後,validated()的結果是空陣列,沒有神奇地補出一個name。
這個空陣列很重要。驗證通過,表示送來的資料符合這組條件,不表示每個可選欄位都存在。後續程式若直接讀 $validated['name'],仍可能遇到不存在的key。請根據這份資料契約,區分「未送不改」與「明確送null要清空」。
別把預設值當成使用者送來的修改
部分更新時,有些寫法會先把所有欄位補成null,接著一次儲存。這樣做會把缺席的欄位與明確清空混成同一件事,原本只改電話的請求可能把暱稱一起清掉。規則本身即使選對,資料整理方式仍會改變結果。
在已驗證的資料中,可用key是否存在決定要不要更新。以下只示意name這個已經獲得授權修改的欄位,不是完整的模型保存程式。
if (array_key_exists('name', $validated)) {
$user->name = $validated['name'];
}
選 array_key_exists是為了保留值為null但key存在的情況;使用 isset會把null看成不成立。若規則不允許null,這一點仍可讓流程更清楚;若允許明確清空,則更不能混用。驗證與資源修改權限要各自核對,通過字串驗證不代表可以修改任何會員。
實際更新還需考慮模型欄位、資料庫約束與業務條件。比如資料庫name欄位禁止null,就不能在API契約裡直接允許清空又不處理儲存。先把前端輸入、Validator輸出與資料庫期待對齊,再寫更新動作,才不會前面通過、後面才失敗。
HTTP表單會經過輸入整理,Tinker不會自動代做
Laravel預設的請求流程會使用字串修剪與空字串轉null等中介層。本文直接Validator的案例沒有經過HTTP中介層,所以空字串保持空字串;放回正常請求時,它可能在進入驗證前已被轉成null。這會影響你看到的驗證資料,但不會讓缺key等於有key。
測試網站時,記下實際送出的payload以及進入驗證時的資料型別。不要在日誌保存密碼、權杖或完整個人資料;只記需要辨認的key與是否null,或使用合成資料。若專案已調整中介層,行為應以自己的設定為準,不能假定每個專案都和預設一樣。
同樣的畫面欄位,HTML表單可能送空字串,JSON呼叫可能送null,而前端程式也可能直接省略。若跨多個客戶端共用API,應把「省略、清空、設值」寫成明確約定,讓各端能產生同樣的意義,而不是靠伺服器猜測。
更新表單要驗兩條路
先試只送電話、不送name,確認暱稱保持原值;再試明確送name,確認它按規則接受或拒絕。若允許清空,另加送null的案例,確認資料庫與畫面都能承接。這三個結果比單看一份驗證成功回應更有用。
新增表單和更新表單也可以採不同的規則:新增資料可能要求名稱一定存在,部分更新則允許名稱缺席。不要為了共用一組字串,把兩者的需求抹成同一個。需要共用格式檢查時可以共用那部分,但key是否必須出現,仍依每個入口決定。
查「欄位沒送卻過關」時,先看是不是刻意用了sometimes,再看validated輸出與更新方式。規則如符合契約,就不必把它改成全部required;真正要修的,可能是下游程式把可選欄位當成必然存在。
還有一個命名容易混淆:規則陣列裡的 sometimes,與Validator物件上用條件追加規則的 sometimes()方法,不是同一段寫法。本文測試的是規則字串的可選欄位行為;若專案用方法依別的輸入追加規則,要連同條件函式一起閱讀。只搜尋名稱相同,不能保證找到的是同一層條件。
維護者可以把四種輸入保存為固定的測試資料,每份寫清楚預期結果。missing應通過哪組、null是否代表清空、空字串是否先整理、合法值如何保存,讓後來改前端的人不必靠猜。若需求變了,先更新這份契約與案例,再調整規則,會比只替某個當下失敗的請求加nullable更穩定。
尤其不要把省略欄位自動補成空字串,這會在驗證前改變請求原本的意思。

評論0