iT邦幫忙

2023 iThome 鐵人賽

DAY 9
0
Software Development

Laravel 後端菜鳥可以知道的流程概念系列 第 9

異常處理 Exception:Route Model Binding 無此資料的情況

  • 分享至 

  • xImage
  •  

昨天有講到 routes 可以與模型 繫結 Binding,讓 url 的參數在傳入 controller 時直接就找到 database 裡面的那一筆資料

https://ithelp.ithome.com.tw/upload/images/20230923/20162893UQe5CXmPJ8.png

但如果,使用者在網址輸入 www.example.com/prosuct/5,試圖查看 id = 5 的產品,但這筆資料已經被 刪除 / 軟刪除 / 或是根本沒有這一筆資料呢?

Laravel 預設會直接給出 404 頁面,這個畫面沒有你的網站logo、沒有選單列、更沒有返回首頁。

真的只想取代這個 404頁面的話,官方文件也有提供取代方法。

https://ithelp.ithome.com.tw/upload/images/20230923/20162893BJcyytbGUQ.png

若是純後端,則會像下面這樣報錯

https://ithelp.ithome.com.tw/upload/images/20230923/20162893izu5jNaQNO.png

這個訊息雖然不影響系統,但會有兩個問題:

  1. 對使用者體驗會很差,使用者只會看到 404 錯誤頁面,而根本不知道是自己取錯資料
  2. 間接暴露了 Backend 端系統設計,有心人士可能藉機利用

Laravel 的異常處理 Exception

官方文件中文版官方文件

Laravel 中的 "exception"(異常)是指在應用程序運行過程中可能引發的意外錯誤或異常情況。異常是一種特殊的事件,它可能會中斷正常的程式執行流程,並且通常需要特殊的處理來妥善處理錯誤情況。Laravel 使用異常處理來提供統一且清晰的方式來處理這些異常,以確保應用程序的穩定性和可讀性。

以下是有關 Laravel 中異常的一些重要概念:

  1. 異常類型:Laravel 定義了多種異常類型,涵蓋了各種可能的錯誤情況,如 ModelNotFoundException(當試圖查找 model 時找不到數據)、ValidationException(當輸入驗證失敗時引發)等。每種異常類型都代表不同的錯誤情況。
  2. 全局異常處理程序:Laravel 提供了一個全局異常處理程序,通常位於 App/Exceptions/Handler.php 文件中。這個處理程序允許您捕獲應用程序中引發的異常,然後根據異常的類型和上下文來執行特定的處理操作。這包括返回適當的 HTTP 響應、紀錄錯誤信息等。
  3. 自訂異常處理:您可以定義自訂的異常類型和處理邏輯,以處理您應用程序中的特定錯誤情況。這有助於更好地控制異常處理流程,使其適應您的應用程序需求。
  4. 異常捕獲:在您的應用程序代碼中,您可以使用 try...catch 塊來捕獲異常,以防止它們中斷代碼的執行。這使您可以在發生錯誤時進行適當的處理,而不會導致應用程序崩潰。

如何解決 Model Binding 下,找不到資料的問題

Laravel 框架有完整的架構,要解決這個問題也可以從很多方法進行處理。(程式語言很多時候沒有 正解 ,需要依需求找尋最適合的解答)

解法一:在 App/Exceptions/Handler.php 的 register() 撰寫異常處理方法

官方文件中文官方文件參考教學

當異常發生時,Laravel 預設會到 App/Exceptions/Handler.php 檔案找尋是否有定義錯誤處理方式,沒有的話就會以內建的預設處理方式進行 response。因此我們可以到 App/Exceptions/Handler.php 檔案中撰寫希望的錯誤處理方式

它就像是設定一個「全局錯誤應對計劃」,在應用程序啟動時,你告訴應用程序應該如何處理所有可能的錯誤情況。

  • reportable() 方法定義當 ModelNotFoundException 發生時應該執行什麼操作,如:記錄異常或通知到外部服務。

    // App/Exceptions/Handler.php
    
    use Illuminate\Database\Eloquent\ModelNotFoundException;
    
    public function register()
    {
        $this->reportable(function (ModelNotFoundException $exception) {
            // 在這裡執行報告異常的操作,例如將其記錄到日誌或通知到外部服務
        });
    }
    
  • renderable() 方法定義當 ModelNotFoundException 發生時應該返回什麼樣的 HTTP 響應,如:自訂錯誤頁面或 JSON 響應。

    use Illuminate\Database\Eloquent\ModelNotFoundException;
    
    public function register()
    {
        $this->renderable(function (ModelNotFoundException $exception, $request) {
            // 在這裡定義當 ModelNotFoundException 發生時的響應,例如返回 404 頁面或 JSON 響應
            return response()->json(['message' => '找不到相關資料'], 404);
        });
    }
    
    //另一種寫法
    public function register()
    {
        $this->renderable(function (NotFoundHttpException $e, $request) {
            if ($request->is('api/product/*')) { // <- 如果來自被定義的 route
                return response()->json([
                    'message' => '資料庫無此筆資料,請重新確認'
                ],
                    Response::HTTP_NOT_FOUND);
            }
        });
    }
    

這個方法在底層邏輯是到了 controller , 發現找不到所屬 Model 資料時拋出異常

https://ithelp.ithome.com.tw/upload/images/20230923/20162893nDiESOxLls.png

解法二:在 App/Exceptions/Handler.php 的 render() 撰寫異常處理方法

官方文件中文官方文件

預設的 Laravel 專案可能沒有此方法,可以自行撰寫新增。

render() 方法可針對不同類型的錯誤客製化回覆,例如:ValidationException、AuthorizationException、FileNotFoundException、ModelNotFoundException……等。

public function render($request, Throwable $e)
{
    if ($e instanceof **ModelNotFoundException**) {  // 確切定義異常類型是哪一種
        return response([
            'message' => '無此資料,請重新確認'
        ]);
    }

    return parent::render($request, $e);
}

解法三:不等到出現錯誤,直接在各個 Model 裡面定義 resolveRouteBinding() 方法

官方文件中文版官方文件

這個方法在 route 時就會查找是否有這筆資料,並用 abort_if() 回傳客製化訊息,不會產生**ModelNotFoundException**,也不會進到 controller,對系統負擔更小。

這個方法可針對每個 model 設定不一樣的提示訊息,如:無此產品、無此圖片…等。

public function resolveRouteBinding($value, $field = null) // $field 是拿去搜尋 table 裡面的哪個欄位,預設為 id
{
    abort_if(
        !$this->where('id', $value)->first(),
        Response::HTTP_BAD_REQUEST,
        __("無此產品,請重新確認")
    );
    return $this->where('id', $value)->first();
}

// 如果要用 table 的其他欄位查找
public function resolveRouteBinding($value, $field = 'tag')
{
    // 使用 $field 欄位的值來比對路由參數
    return $this->where($field, $value)->first();
}

https://ithelp.ithome.com.tw/upload/images/20230923/201628932lxmnoIb4Y.png

解法四:直接在 route 定義找不到資料時的行動

官方文件中文版官方文件

可以在定義 Route 時一併定義 missing 方法來自訂這個行為。missing 方法接受一個閉包,該閉包會在找不到隱式繫結的 Model 時被叫用:

use App\Http\Controllers\LocationsController;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Redirect;
 
Route::get('/product/{product:slug}', [ProductController::class, 'show'])
    ->missing(function (Request $request) {
        return Redirect::route('/product'); // 重導向到別的 route
    });

四個解法比較

https://ithelp.ithome.com.tw/upload/images/20230924/2016289371GOdmKNVm.png


上一篇
Laravel routes
下一篇
Laravel 如何與 Database 互動
系列文
Laravel 後端菜鳥可以知道的流程概念30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言