Laravel查不到資料還讀欄位:first的null與firstOrFail

商品詳情頁偶爾報錯「無法讀取null的屬性」,但同一段程式在別的商品又正常。先查單筆查詢是否真的有找到模型。Eloquent的 first()沒有找到資料時會回null;接著直接讀 $item->name,就把「查無資料」變成另一個程式錯誤。

你可以用明確的null分支處理沒有資料,也可以在「這個入口必須有一筆」時使用 firstOrFail(),讓未找到以例外表達。兩者是不同的流程選擇,不是只因為方法名字看起來較強,就全部替換。

first只取第一筆,不保證一定有

下面的查詢以主鍵篩選。如果資料存在,first得到模型;若主鍵不存在,結果是null。這與查詢沒有連上資料庫不同,也不代表資料庫操作拋出例外。

$item = Item::whereKey($id)->first();
if ($item === null) {
    // 依這個入口的需求處理查無資料。
} else {
    $name = $item->name;
}

把讀屬性的動作放在確認模型存在的分支後,能讓維護者直接看出條件。不要只在每個欄位讀取前加可選操作符,讓整頁默默顯示空值,卻沒有決定這條商品URL不存在時應怎麼回應。

也不要把資料庫故障與查無資料混成同一個空畫面。first正常返回null,與連線或SQL錯誤是不同情況;後者仍可能拋出例外,需要沿專案既有錯誤處理方式處理。一次廣泛catch後回空字串,很容易把真正的服務故障藏起來。

必須存在的資源可用firstOrFail

對商品詳情、會員資源等需要明確模型的入口,firstOrFail可以讓查無資料時丟出 ModelNotFoundException。找到資料時仍回傳模型,並不是讓成功資料有另一種形狀。

$item = Item::whereKey($id)->firstOrFail();
$name = $item->name;

Laravel官方文件說明,這個例外在應用程式一般HTTP處理中若未被捕捉,會產生404回應。本文本機案例只核Eloquent例外類型,沒有啟動完整HTTP控制器;接回你的網站後,仍要核專案的例外處理、回應格式與路由配置。

如果你已經有自訂handler,把所有例外都轉成200或其他格式,不能只看firstOrFail方法就認為最終HTTP必然正確。查詢層表達未找到,與應用程式把它轉成對使用者的回應,是接續的兩層。

用存在與不存在ID做對照

在專用測試資料庫建立一筆合成商品,名稱Demo。先用它的主鍵查,再用一個確定不存在的主鍵查,不接正式商品庫,也不需要刪掉既有商品來製造情況。

$found = Item::whereKey($existingId)->first();
$missing = Item::whereKey($missingId)->first();
dump($found?->name);
dump($missing === null);
try {
    Item::whereKey($missingId)->firstOrFail();
} catch (\Illuminate\Database\Eloquent\ModelNotFoundException $e) {
    dump(get_class($e));
}

本文隔離案例使用PHP8.4.2、Laravel13.34.0與記憶體SQLite。存在ID的first得到名稱Demo;不存在ID的first確實是null;同一不存在ID的firstOrFail拋出完整的Eloquent ModelNotFoundException類型。沒有任何正式資料被改動。

這三項比較回答的是「查詢方法如何表達未找到」。它不包含瀏覽器404頁、API錯誤格式或權限控制的驗收。讀者可以在自己的測試路由再做真請求,分別確認成功回應與未找到回應,避免把本機例外輸出當成完整網站行為。

未找到可能來自查詢範圍

資料表裡看得到一筆,應用程式卻查不到,不一定是first有問題。查詢還可能包含狀態、租戶、軟刪除或全域scope。先看真正執行的條件,確認這份資料是否應在目前入口的可見範圍內。

不要為了讓查詢拿到資料就移除所有scope或授權條件。它們可能正是防止跨租戶或未公開資料被讀取的界線。若結果與需求不符,依具體條件修正,而不是把where清空後拿第一筆任意資料。

如果查的是email、slug等非主鍵欄位,也要核對它們是否真正唯一。first只取符合條件的第一筆,並不會替你確認只有一筆。遇到重複資料時,任意取第一筆可能掩蓋資料品質問題,不能把「沒有報錯」當成資源解析正確。

列表無結果,和詳情不存在不同

搜尋清單沒有符合項目,通常可以顯示空結果;單一資源URL找不到對應項目,則可能適合未找到回應。不能為了統一方法,把所有查詢都改成firstOrFail,讓正常的零筆搜尋也成為異常。

同樣地,first不是拿來代表一份完整列表。列表查詢應依需求取得集合或分頁,並在集合層處理零筆;單筆查詢才看模型或null。先分清UI到底要一個資源還是多筆結果,再選對應方法。

在實際網站中,無結果訊息可引導使用者調整篩選;未找到資源則應提供返回合理入口的方式。這是各入口的產品行為,查詢方法只提供資料層的條件,不會自動幫你設計畫面。

別在缺資料時製造假的成功模型

有些修法會用空Item或預設名稱替代null,讓屬性讀取不報錯。如果這條URL本來應對應一筆真資料,假模型可能讓頁面回正常成功,卻顯示不存在的商品。只有需求本來允許預設資料時,才應採這種策略。

修改或刪除入口更需要確認模型存在,不能在null時繼續執行後續動作。先把資源解析清楚,再核權限與操作條件,最後才寫入。firstOrFail不會代替授權,也不會證明這筆資料可由目前使用者修改。

如果在查詢與寫入之間,資料可能被另一個操作變更,還需按業務規則處理競爭情況。本文的合成案例沒有併發,不把單次查到模型推成整段修改流程永遠有效。對需要交易或鎖的功能,另依真正的資料一致性要求設計。

從讀屬性的位置往前查

遇到null屬性錯誤,先找到模型變數從哪裡來,核查詢與未找到分支。若應容許不存在,就明確處理null;若必須存在,就用適合的未找到流程,並核HTTP層的真結果。不要在整篇畫面每個欄位加預設值,卻留下資源其實沒有找到的問題。

保存存在ID與不存在ID兩份固定案例,往後調scope或路由時一起重測。這樣能分辨是資料範圍有意改變,還是查詢流程意外失效,也讓下一位維護者知道查無資料應該走哪條路。

最後確認你用的是Eloquent模型查詢。DB::table()的Query Builder成功取到單筆時,返回的是一般物件,不是帶有模型方法與關聯的Item。兩者都能讀某些欄位,不代表可直接交換使用;也不能對一般物件呼叫模型的保存或關聯方法。本文比較的是Item模型的first與firstOrFail,維護另一種查詢時應按它自己的返回型別核對。

參考資料

原文鏈接:https://wntheme.com/laravel-first-null-first-or-fail/,轉載請註明出處。
0

評論0

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