淺談 PHP 中異常類的使用

minororange發表於2019-11-16

為什麼要使用異常類

  1. 優化程式碼的可讀性

    在沒有異常的時候我們在編寫函式時往往會出現以下情況:

    
    function doSomething(){
    if(something error){
       return false;
    }
    
    ...
    do something
    }
    //呼叫
    $result = doSomething();
    
    if(!$result){
     echo "something goes error";
    }

    如果使用異常則可以這麼寫:

    function doSomething(){
        if(something error){
             throw new SomethingException('something error');
        }
        ...
        do something
    }
    //呼叫
    try{
       doSomething();
    }catch(SomethingException $e){
       echo "something goes error";
    }

    無論時呼叫層面還是函式本身層面,丟擲異常的可讀性比返回 false 的可讀性要更高,因為我們能夠一眼就看出來哪些程式碼是異常時執行的,哪些時正常時執行的。

  2. 更加符合語義化

    異常類往往都有自己的名字,在函式呼叫層面,即使不進入函式內部也能夠通過異常名來判斷呼叫函式會伴有哪些異常,如果函式沒有異常,返回的是 falsetrue ,這會讓函式的呼叫者非常頭痛,因為你永遠不知道這個函式呼叫什麼時候是異常的,只能通過返回值判斷,一個函式如果有多種異常情況,通過返回值判斷就會在呼叫層面生成多個 if,如果使用了異常類,呼叫層能夠輕鬆的分別 catch 不同的異常來進行處理。

    // 呼叫方無法從外部得知時哪裡出了問題,因為只返回了 false
    function checkoutOrder($orderNumber){
      $orderModel = OrderModel::query()->where('order_number',$orderNumber)->first();
      if(!$orderModel){
         return false;
      }
      $payResut = PayService::pay($orderModel);
      if(!$payResut){
          return false;
      }
    }

    為每種程式異常命名:

    function checkoutOrder($orderNumber){
      $orderModel = OrderModel::query()->where('order_number',$orderNumber)->first();
      if(!$orderModel){
         throw new OrderNotFoundException($orderNumber);
      }
      $payResut = PayService::pay($orderModel);
      if(!$payResut){
          throw new PaymentException(); // 這個異常應該上面的 PayService 中丟擲,為了更清晰就寫在這
      }
    }
    
    // 呼叫
    try{
        checkoutOrder('ORDER00001');
    }catch(OrderNotFoundException $e){
        return response('訂單不存在:'.$e->getMessage(),404);
    }catch(PaymentException $e){
        return response('支付失敗:'.$e->getMessage(),500);
    }

    在 Laravel 中使用異常類

    • Laravel 異常處理流程:

      淺談 PHP 中異常類的使用

    注:如果在控制器中 catch 最底層的 \Exception ,異常就不會走到 Handler 裡面,未被 catch 掉的 \Exception 都會記錄在 storage/logs/laravel.log 中,所以在控制器中 catch 異常要考慮清楚,否則可能在日誌檔案中查詢不到錯誤原因。

    • 使用 Handler 做專案錯誤告警

      app/Exceptions/Handler.php 中根據異常名、緊急程度呼叫第三方通知工具(釘釘、郵件等)通知專案錯誤。

      public function report(Exception $exception){
          if ($this->shouldntReport($exception)) {
              return;
          }
          // 如果異常類中存在 report 方法,就使用自身的
          if (method_exists($exception, 'report')) {
              return $exception->report();
          }
      
          $msg = "系統異常:" . $exception->getMessage();
          $msg .= "\n檔案:" . $exception->getFile();
          $msg .= "\n行號:" . $exception->getLine();
          $msg .= "\n引數:" . json_encode(['form_params' => request()->all()]);
      
          DingService::sendWarning($msg);
          parent::report($exception);
      }
    • render 方法根據異常名返回不同的客戶端響應:

    public function render($request, Exception $exception)
    {
        if ($exception instanceof OrderException) {
            return $this->handleOrderException($exception, $request);
        }
        if ($exception instanceof PaymentException) {
            return $this->handlePaymentException($exception, $request);
        }
        return parent::render($request, $exception);
    }

    更多詳細說明可檢視 Laravel--錯誤處理

相關文章