Throwable / Error / Exceptionの階層を理解する

Throwableは投げられるものすべての頂点となるインターフェースで、その下に業務例外のExceptionとエンジンレベルのErrorが分かれます。この階層でcatchの基準を決めます。

頂点はThrowableインターフェース

PHP 7 以降、投げられるものすべての頂点に Throwable インターフェースがあります。その下に大きく Exception(アプリケーションの例外)と Error(PHP エンジンレベルの重大エラー)の二系統がぶら下がる構造です。この二本立てを知っておくと、catch の書き方に迷わなくなりますね。

// 階層のイメージ
// Throwable (interface)
//   ├── Exception  ── RuntimeException, LogicException ...
//   └── Error      ── TypeError, ValueError, DivisionByZeroError ...

try {
    // ...
} catch (Throwable $e) {
    // Exception も Error も両方ここで拾える
}

昔は catch (Exception $e) で全部拾えた時代の名残で、Error 系を取りこぼしているコードをたまに見かけます。

ErrorとExceptionの守備範囲が違う

Exception は業務ロジックで意図的に投げる例外、Error は型不一致(TypeError)や存在しない関数の呼び出しなど、本来はコードのバグに近いものです。PHP 8 ではゼロ除算が DivisionByZeroError、不正な引数値が ValueError として Error 系で飛んでくるようになりました。

function half(int $n): int
{
    return intdiv($n, 2);
}

try {
    half('文字列'); // TypeError (Error 系)
    intdiv(1, 0);  // DivisionByZeroError (Error 系)
} catch (Exception $e) {
    // これでは上記は捕まらない!
}

catch (Exception) だけだと Error 系はすり抜けて致命的エラーになります。ここが移行時にハマりやすいところですね。

catchの基準をどう置くか

基本方針としては、想定内の失敗は具体的な例外型で個別に、最上位のセーフティネットでは Throwable を拾う、という二段構えが扱いやすいです。エントリポイントに Throwable の catch を一つ置いて、そこでログとエラーページ表示をまとめる、というのが私のいつもの形です。

try {
    $result = $service->run($request);
} catch (ValidationException $e) {
    // 想定内: 具体的な型で丁寧に扱う
    return respondBadRequest($e->getMessage());
} catch (Throwable $e) {
    // 想定外の Error も含めて最後の砦で受ける
    error_log((string)$e);
    return respondServerError();
}

まとめ

投げられるものの頂点は Throwable で、その下に業務例外の Exception とエンジンレベルの Error が分かれています。PHP 8 では型エラーやゼロ除算が Error 系で飛ぶので、catch (Exception) だけでは取りこぼします。想定内の失敗は具体型で、最後のセーフティネットは Throwable で受ける。この基準で組んでおけば、想定外のバグも静かに致命的エラーにならずに済むと思います。

よくある質問

Q. catch (Exception) で全部拾えますか。
A. いいえ。TypeErrorやDivisionByZeroErrorなどError系はExceptionではないので取りこぼします。

Q. すべての例外を確実に受けるには。
A. 頂点の Throwable でcatchすると、ExceptionもErrorも両方拾えます。

Q. catchはどう組むのがよいですか。
A. 想定内の失敗は具体的な例外型で個別に、最後のセーフティネットは Throwable で受ける二段構えが扱いやすいです。

類似投稿

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です