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