Laravel表單欄位沒存到?分清fillable、驗證與直接賦值

會員編輯姓名的表單只露出一個輸入框,後端卻可能收到其他欄位。瀏覽器沒有顯示 is_admin,不代表請求裡不能加上它。另一種常見情況恰好相反:表單新增了電話,驗證也通過,儲存後電話卻消失。這兩個問題都值得沿著資料進入模型的路線查,不能只看畫面。

Laravel的 fillable 控制模型接受哪些大量賦值欄位;表單驗證控制輸入的內容與格式;授權則回答這個人能不能修改這筆資料。三者處理不同問題。本文以一般會員修改自己的公開姓名為例,說明欄位白名單怎麼寫,以及哪些寫法會繞過它。以下語法適用本文示範的Laravel 13 Eloquent模型,舊專案請先對照自己的框架版本。

先找出欄位從哪裡進入模型

看到 create()、模型實例的 update() 或 fill(),先看傳入的陣列由哪裡來。如果直接送入 $request->all(),任何請求欄位都會走到模型的大量賦值檢查,維護者必須知道哪些被接受、哪些被丟棄。表單畫面不能代替這份檢查。

class Profile extends Model
{
    protected $fillable = ['name'];
}

$profile = new Profile;
$profile->fill([
    'name' => 'Coffee',
    'is_admin' => true,
]);

這個例子在模型屬性裡只留下 name。預設行為下,沒有列在白名單的 is_admin 被丟棄。這不表示使用者已經存進資料庫:fill() 只是填模型屬性,真正寫入仍需要儲存操作。要確認一個實際表單的結果,應在送出後重新讀取資料庫那筆資料,不能只用回傳訊息或記憶體中的物件作結論。

對既有專案,先搜尋模型名稱及它的寫入位置。相同模型可能被會員表單、管理後台與匯入工具共用;把某欄加到白名單會影響所有大量賦值入口。如果只有管理員可以修改會員等級,不應為了讓後台少寫一行程式,就把等級開放給所有送進該模型的陣列。

驗證完成後,只送需要的資料

會員修改姓名的入口可以先用驗證取得指定資料,再交給模型。白名單也保留,讓兩個邊界互相補足。下面假設 $profile 已經是目前登入會員有權修改的資料;查出哪筆資料及授權檢查,仍要在你的控制器或Policy裡處理。

$data = $request->validate([
    'name' => [
        'required',
        'string',
        'max:80',
    ],
]);

$profile->fill($data);
$profile->save();

不要把驗證當作權限檢查。姓名格式正確,不表示送出的人能編輯其他會員;同樣,模型接受某欄,不代表該欄適合由這個表單修改。如果網站有巢狀資料,還要明確限制允許的陣列鍵,不能因為驗證了上層陣列就假設其中每個鍵都已逐項審核。

若新增電話欄位,至少沿四處核對:前端欄位名稱、驗證規則、送進模型的資料、模型白名單。最後確認資料表真有該欄,以及型別和長度能容納輸入。只改 fillable 不會新增資料庫欄位,反過來只加migration也不會讓表單自動接受資料。

直接指定屬性不受同一白名單限制

下面這段是另一條寫入路徑。它不是把整個輸入陣列交給大量賦值,因此不會因為 is_admin 沒列在 fillable 就被擋下。本文案例中,直接指定後的模型屬性包含 name 與 is_admin。

$profile->is_admin = true;
$profile->save();

這種寫法本身有合理用途,例如伺服器依已核准的管理操作更新狀態。問題是值從哪裡來,以及是否先做授權。如果把請求中的權限欄位直接接到這裡,白名單不能救回這條路徑。對照程式時,不能只搜尋 create() 而漏掉屬性指定、Query Builder更新或原始SQL。

也不要把 $guarded = [] 當作表單不能儲存的通用修法。它會讓模型接受更多大量賦值欄位,資料整理責任轉到每個呼叫點。維護者若無法逐一確認所有入口,開放整個模型會讓後續新增欄位更難評估。對需要JSON巢狀鍵的模型,請按框架文件列明允許的鍵,而不是用寬泛設定替代輸入設計。

開發時讓漏掉的欄位發出訊號

新增欄位後「沒報錯但沒存到」很容易讓人把問題錯認為快取。在開發環境,可啟用大量賦值的嚴格提醒,讓被丟棄的屬性拋出例外。這是發現模型設定與輸入不一致的方法,不是向正式訪客顯示詳細錯誤的理由。

use Illuminate\Database\Eloquent\Model;

Model::preventSilentlyDiscardingAttributes(
    app()->isLocal()
);

把設定放在專案正常啟動的Service Provider位置,確認測試環境的環境名稱。本文用同一組輸入啟用提醒後,收到 MassAssignmentException;關閉提醒時,未允許欄位不出現在模型屬性。這個差異可以幫助定位原因,但仍不能證明整個會員表單的授權與儲存流程都正確。

用兩組請求確認修正範圍

第一組只送合法姓名,確認已登入會員能更新自己的公開名稱,而且重新查詢後值一致。第二組多帶權限欄位,確認該欄不會由會員入口改變。接著換另一位會員的資料ID,確認授權拒絕;這一步檢查資料所有權,不能用白名單結果取代。

測試時保留原有權限值,送出後比較值是否維持,不要只看HTTP狀態。若管理員入口本來需要調整等級,另外測管理入口在授權成功後的指定寫入,避免修會員表單時把合法後台操作一起關掉。用可辨識的測試帳號及合成資料,先在測試環境跑,不在正式會員身上試改權限。

若資料仍沒存進去,再往驗證回傳、模型事件、交易是否提交與讀取來源查。讀取副本或快取也可能讓畫面暫時顯示舊值,這時需核對真正寫入來源。把所有疑點都歸給 fillable,很容易在錯的層增加設定,讓問題更難追。

交接這類修正時,把會員可以改的欄位與伺服器自行維護的欄位分開列明。建立者ID、管理權限與審核狀態通常來自登入身分或業務規則,不能因為請求中出現了同名欄位就直接採用。讓後續維護者看得出值的來源,才不會在新增表單時重新打開已經收窄的入口。

權限與欄位分開維護

當你需要釐清誰能更新這筆資料,可接著看 Laravel用Policy檢查資料所有權。若只是選填欄位空白被驗證擋下,則參考 nullable與格式檢查。先找對處理問題的位置,再修改對應設定,通常比一次放寬模型、驗證與權限更容易確認影響。

示例環境:Laravel 13.34.0、PHP 8.4.2;合成模型屬性比較及例外行為,未連接會員服務。實際專案另核資料表、授權、儲存及重新讀取結果。

參考資料

原文鏈接:https://wntheme.com/laravel-fillable-input-boundary/,轉載請註明出處。
0

評論0

顯示驗證碼
沒有帳號?註冊  忘記密碼?