ch07 8章中 7章目

ch07 — 例外の再 throw (rethrow)

スライドで見る

この章を AI と学ぶ

💬 AI に聞く質問例 3 件
  1. 「ch07 — 例外の再 throw (rethrow)」のつまずきやすいポイントを、初学者向けに3つ教えてください

    ChatGPT Claude Gemini

  2. 「ch07 — 例外の再 throw (rethrow)」について、自分の手で試せる練習問題を3つ出してください (難易度を上げながら)

    ChatGPT Claude Gemini

  3. 「ch07 — 例外の再 throw (rethrow)」を学んだ後、次に学ぶと良いトピックは何ですか?理由も教えてください

    ChatGPT Claude Gemini

概要

DB 層が PDOException("SQLSTATE[23000]...") のような低レベルな例外を投げてきたとき、それを そのまま画面層まで届ける のは設計として弱い。画面層は SQL のことを知らないし、知るべきでもない。レイヤーをまたぐ境界では、そのレイヤーの言葉に翻訳された例外 に変換した方が、上層は扱いやすい。

PHP の catch ブロックの中で throw new XxxError(...) と書けば、受け取った例外を踏み台にして 別の例外に包んで投げ直す ことができる。これを rethrow (再 throw) と呼ぶ。たとえば DB 層で PDOException を catch して、ドメイン層では UserRegistrationFailedError として上に伝える。catch 側 (画面層) はこの上位の型だけ知っていればよくなる。

このとき大事なのが previous 引数new XxxError("失敗", 0, $e) のように元の例外を 3 番目に渡しておくと、$e->getPrevious() で元の例外を辿れる。ログには元の SQL エラーまで残せて、画面にはドメイン側のメッセージを出す、という両立ができる。レイヤー境界での「翻訳しつつ証拠も残す」定石パターン。

学習目標

  • catch で受けた例外を 別の例外に包んで再 throw できる
  • 低レベルの例外を上位の意味あるエラーに変換する書き方を理解する
  • previous チェーンで元の例外も残せると知っている

ドリル

このチャプターはスライドのみ。次の章へ。

採点ドリルは無し。スライドで概念を理解したら ch08 (validation-pattern) に進む。

解説スライド 全 6 枚

右の ボタン キー(またはスワイプ)で次のスライドに進めます。 下の「縦表示」を開くと全スライドを一気に読めます。

  1. 例外の再 throw (rethrow)

    Lesson 10 / Chapter 7

  2. 低レベルの例外をそのまま投げると意味が伝わらない

    <?php
    function loadUser(int $id) {
        try {
            // DB アクセス (失敗すると PDOException)
            $row = dbFetch($id);
        } catch (PDOException $e) {
            throw $e;   // ← そのまま投げる
        }
        return $row;
    }
    • 呼び出し元には PDOException がそのまま伝わる
    • 「DB の話」が「業務ロジック」のレイヤーまで漏れてしまう
  3. 上位の意味で包み直す

    <?php
    class UserLoadError extends Exception {}
    
    function loadUser(int $id) {
        try {
            $row = dbFetch($id);
        } catch (PDOException $e) {
            throw new UserLoadError("ユーザー読み込み失敗", 0, $e);
        }
        return $row;
    }
    • catch で受けた後、UserLoadError という 業務的な名前 の例外を投げ直す
    • 呼び出し元は「ユーザーの読み込みが失敗した」とだけ知れば良い
  4. 第 3 引数で元の例外を保持できる

    <?php
    throw new UserLoadError("読み込み失敗", 0, $e);
    //                          ↑ message  code  previous
    • 第 3 引数に元の例外を渡せる
    • 後で $e->getPrevious() で元の例外を取り出せる
    • ログには両方残せるので、デバッグ情報を失わない
  5. レイヤー間の翻訳パターン

    低レベル          中レベル                 高レベル
    PDOException → UserRepositoryException → ApplicationError
                  (DB の事情)                (業務的に何が起きたか)
    • 各層で適切な名前に「翻訳」していく
    • 上の層は下の層の実装詳細を知らなくて良い
  6. このチャプターでできるようになること

    ✅ catch した例外を別の例外で包んで再 throw できる ✅ レイヤーに応じて例外の名前を変える発想を持てる ✅ previous で元の例外を保持できると知っている

    関連: 同トピック内の他チャプター (このチャプターは採点ドリル無し)

1 / 6
📜 スライドを全部一気に読む(縦表示)

Lesson 10 / Chapter 7


低レベルの例外をそのまま投げると意味が伝わらない

<?php
function loadUser(int $id) {
    try {
        // DB アクセス (失敗すると PDOException)
        $row = dbFetch($id);
    } catch (PDOException $e) {
        throw $e;   // ← そのまま投げる
    }
    return $row;
}
  • 呼び出し元には PDOException がそのまま伝わる
  • 「DB の話」が「業務ロジック」のレイヤーまで漏れてしまう

上位の意味で包み直す

<?php
class UserLoadError extends Exception {}

function loadUser(int $id) {
    try {
        $row = dbFetch($id);
    } catch (PDOException $e) {
        throw new UserLoadError("ユーザー読み込み失敗", 0, $e);
    }
    return $row;
}
  • catch で受けた後、UserLoadError という 業務的な名前 の例外を投げ直す
  • 呼び出し元は「ユーザーの読み込みが失敗した」とだけ知れば良い

第 3 引数で元の例外を保持できる

<?php
throw new UserLoadError("読み込み失敗", 0, $e);
//                          ↑ message  code  previous
  • 第 3 引数に元の例外を渡せる
  • 後で $e->getPrevious() で元の例外を取り出せる
  • ログには両方残せるので、デバッグ情報を失わない

レイヤー間の翻訳パターン

低レベル          中レベル                 高レベル
PDOException → UserRepositoryException → ApplicationError
              (DB の事情)                (業務的に何が起きたか)
  • 各層で適切な名前に「翻訳」していく
  • 上の層は下の層の実装詳細を知らなくて良い

このチャプターでできるようになること

✅ catch した例外を別の例外で包んで再 throw できる ✅ レイヤーに応じて例外の名前を変える発想を持てる ✅ previous で元の例外を保持できると知っている

関連: 同トピック内の他チャプター (このチャプターは採点ドリル無し)

完了にすると進捗に反映されます