# ch07 — 例外の再 throw (rethrow)

Source: https://php-school.pages.dev/topics/exception/ch07-rethrow/
Site: AIと学ぶPHPの学校 (https://php-school.pages.dev)
Lesson: 例外処理
Level: L3

Reference (official): https://www.php.net/manual/ja/language.exceptions.php

## 本文

# ch07 — 例外の再 throw (rethrow)

## 概要

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` チェーンで元の例外も残せると知っている

## 所要時間

スライド 4 分 = **4 分**

## ドリル

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

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

## スライド

---
marp: true
theme: dojo
paginate: true
size: 16:9
---

<!--
LLM_CONTEXT:
  Lesson 10 / Chapter 7
  目的: catch した例外を別の意味ある例外に変換して再 throw する設計パターンを理解する
  扱わない: getPrevious() の深い活用 / 例外の階層設計のベストプラクティス
  読み上げ時間目安: 3 分半〜4 分
-->

# 例外の再 throw (rethrow)

Lesson 10 / Chapter 7

---

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

```php
<?php
function loadUser(int $id) {
    try {
        // DB アクセス (失敗すると PDOException)
        $row = dbFetch($id);
    } catch (PDOException $e) {
        throw $e;   // ← そのまま投げる
    }
    return $row;
}
```

- 呼び出し元には `PDOException` がそのまま伝わる
- 「DB の話」が「業務ロジック」のレイヤーまで漏れてしまう

---

## 上位の意味で包み直す

```php
<?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
<?php
throw new UserLoadError("読み込み失敗", 0, $e);
//                          ↑ message  code  previous
```

- 第 3 引数に元の例外を渡せる
- 後で `$e->getPrevious()` で元の例外を取り出せる
- ログには両方残せるので、デバッグ情報を失わない

---

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

```
低レベル          中レベル                 高レベル
PDOException → UserRepositoryException → ApplicationError
              (DB の事情)                (業務的に何が起きたか)
```

- 各層で適切な名前に「翻訳」していく
- 上の層は下の層の実装詳細を知らなくて良い

---

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

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

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