PHP例外処理のベストプラクティスと実装例

PHP開発者の中には例外処理をあまり使わない人もいます。Web開発では迅速な開発、使いやすさ、高性能が重視され、プログラムの堅牢性はあまり強調されないからです。

PHPの例外処理機能は他の言語と比較してまだ完全ではなく、使いやすさも改善の余地があります。

標準的なアプローチは、まずエラーの種類を分類し、それぞれのエラー種類に対して処理方案とメカニズムを策定することです。

例えば、ビジネスロジックレベルのエラー、システムレベルのエラー、致命的なエラーなどに対して、異なるレベルのエラーを処理し、コードの堅牢性とアクセスのユーザーフレンドリーさを向上させます。

class BusinessLogicException extends Exception{
        //コンストラクタを再定義して、最初のパラメータ message を必須のプロパティにする
        public function __construct($errorMessage, $errorCode=0){
            //ここで独自のコードを定義できます
         //parent::construct()を同時に呼び出して、すべての変数が設定されているか確認することを推奨します
            parent::__construct($errorMessage, $errorCode);
        }    
        public function __toString() {        
          //親クラスのメソッドをオーバーライドして、文字列出力のスタイルをカスタマイズします
          return __CLASS__.":[".$this->code."]:".$this->message."<br>";
        }
        public function handleException() {    
             //この例外タイプに対してカスタム処理メソッドを定義します
             echo "このタイプの例外をカスタムメソッドで処理します<br>";
        }
    }

最終的なエラー応答時には、具体的なエラーの種類に応じて処理を行います

コントローラ

 public function handleRequest(Request $request) {
        try {
            // ビジネスロジックを実行
            $result = $this->executeBusinessLogic($request);
            return response()->json(['status' => 200, 'message' => '操作成功', 'data' => $result]);
        } catch (\Exception $exception) {
            return $this->handleError($exception);
        }
function handleError(\Exception $exception) {

    if ($exception instanceof BusinessLogicException) {
        // ビジネスロジック例外の特別処理
        return $this->processBusinessError($exception);
    } elseif ($exception instanceof \Exception) {
        // 一般例外の処理
        return $this->processGeneralError($exception);
    } else {
        throw new \Exception('未知のエラー例外');
    }
    
    // リクエストパラメータを取得
    $requestData = request()->all();
    // ログに記録
    Log::error($exception->getMessage(), ['request' => $requestData]);
    // エラー応答を返す
    return response()->json(['status' => 400, 'message' => $exception->getMessage(), 'details' => formatError($exception)]);
}

以下は一般的に使用されるデモです

注文コントローラ

public function fetchSubOrderPayment(Request $request) {

        try {
            $pageNumber = validateParameter($request->page, 'int', 1);
            $itemsPerPage = validateParameter($request->page_size, 'int', 15);

            $parentOrderKey = validateParameter($request->parent_order_key, 'string', '');

            $orderData = OrderService::getSubOrderPayment($shopId, $parentOrderKey, $pageNumber, $itemsPerPage);

            return response()->json(['status' => 200, 'message' => '操作成功', 'data' => $orderData]);
        } catch (\Exception $exception) {
            return response()->json(['status' => 400, 'message' => $exception->getMessage(), 'details' => formatError($exception)]);
        }
    }
注文サービスクラス
 public static function getSubOrderPayment(int $shopId, string $parentOrderKey, int $page = 1, int $pageSize = 15) {
        try {
            $order = OrderModel::where('is_deleted', 10)
                             ->where('shop_id', $shopId)
                             ->where('parent_order_key', $parentOrderKey)
                             ->where('order_type', 20)
                             ->first();
            
            if (!$order) {
                throw new \Exception('親注文番号が不正または注文データが削除されています');
            }

            $orderList = OrderModel::where('is_deleted', 10)
                                ->where('shop_id', $shopId)
                                ->where('parent_order_key', $parentOrderKey)
                                ->where('order_type', 20)
                                ->get();

            if (empty($orderList)) {
                return ['total' => 0, 'items' => []];
            }
            
            return $orderList;
        } catch (\Exception $exception) {
            throw $exception;
        }
    }
    // エラーメッセージをフォーマットして出力しやすくする
    function formatError(\Exception $exception) {
        $errorInfo['File'] = $exception->getFile();
        $errorInfo['Line'] = $exception->getLine();
        $errorInfo['Message'] = $exception->getMessage();
        $errorInfo['StackTrace'] = $exception->getTraceAsString();

        return $errorInfo;
    }

注意:自分でthrowしないと、Laravelが例外を投げると、特にGETリクエストのときにユーザーフレンドリーな応答が返されません。

個人的には、PHPにJavaのようなthrows宣言を導入してほしいです。こうすれば、多層レベルのコードで多くの不必要なロジックコードの記述を減らせ、直接上位層に投げることができ、使いやすくなります。

public static function calculateTotal() throws InvalidArgumentException{
    // 何らかの計算処理
}

多層ネストされたtry-catch-finallyでの例外捕捉方法

PHPは多層ネスト処理を扱う際、catchは深層から投げられた例外を直接捕捉できず、finallyでしか処理できない場合があります。しかし、時には多層で投げられた例外がメッセージ提示として使用されることがあります。

解決方法は簡単です。例外は上位の例外オブジェクトだけが処理できるため、下位層で例外情報をパッケージ化して再び上位層に送信すれば、捕捉できます。

   try {
        // 何らかの処理
   } catch (\Exception $exception) {
//            throw $exception;
            throw new \Exception($exception->getMessage());
        }

少し面倒ですが、時にはこの方法しかありません。こうすれば、多層ネストの際でも具体的に投げられたエラー情報を捕捉できます。

タグ: PHP 例外処理 laravel エラーハンドリング カスタム例外

8月6日 06:10 投稿