例外処理

try / catch / throw — エラーを安全に扱う

エラーを if の戻り値ではなく 「例外オブジェクトを投げて、別の場所で拾う」 という構造で扱うトピック。

このトピックで身につくこと

  • try / catch / finally を組み合わせてエラー処理を書ける
  • throw new Exception(...) で自分の関数から例外を投げられる
  • 例外の種類で処理を分岐できる
  • カスタム例外クラスで「意味のあるエラー」を表現できる
  • 「戻り値で false を返す」設計と「例外を投げる」設計を使い分けられる

前提知識の要点

このトピックは 「クラスが書ける」「関数が書ける」 の 2 点があれば始められる。さらに 1 つだけ重要な前提として 「Exception はクラスである」「new Exception() でインスタンスを作る」「throw は文 (式ではない)」 を押さえる。他のトピックの README を読み返さずに exception を単独で開始できる ように、以下を圧縮しておく。

1. クラスと new

<?php
class User {
    public function __construct(public string $name) {}
    public function greet(): string {
        return "こんにちは、{$this->name}";
    }
}

$u = new User("太郎");
echo $u->greet();  // => こんにちは、太郎
▶ 3v4l で実行

このトピックで扱う Exception も「PHP に最初から組み込まれているクラスの 1 つ」というだけ。書き方は上と同じ。

2. 関数の引数・戻り値

function divide(int $a, int $b): int {
    return intdiv($a, $b);
}
▶ 3v4l で実行

このトピックでは「この関数は不正な入力のとき何を返す?」の答えとして「例外を投げる」を学ぶ。

3. これだけは新しく覚える: Exception はクラス・throw は文

// new でインスタンスを作る (普通のクラスと同じ)
$e = new Exception("失敗しました");

// throw でその例外を「投げる」(これ以降の行は実行されない)
throw $e;

// 1 行で書くのが普通
throw new Exception("失敗しました");
▶ 3v4l で実行

重要なのは:

  • Exception はクラスnew でインスタンス化する
  • throw はそのインスタンスを投げる文。投げられた瞬間、関数の途中でも実行が止まる
  • 投げられた例外は、呼び出し元の try { ... } catch { ... } で拾える
  • 誰も拾わないと Uncaught Exception でプログラムが落ちる

継承は必須ではない

ch06 (カスタム例外) で extends Exception が出てくるが、「Exception クラスを継承して、独自の名前の例外を作る」というだけの使い方なので、class-advanced を完了していなくても ch06 直前の slide.md を読めば追いつける。

ここまで読めれば ch01 から始められる。

chapter 一覧

chapter 内容 学習目標
ch01-try-catch/ try / catch の基本 例外が起きうるコードを try { } catch { } で囲める
ch02-throw/ throw で例外を投げる 自分の関数から throw new Exception(...) できる
ch03-exception-message/ 例外メッセージの取り出し $e->getMessage() でメッセージを取得できる
ch04-multi-catch/ 複数の例外を catch する 例外の型ごとに処理を分けられる
ch05-finally/ finally 成功/失敗に関わらず必ず通る処理を書ける
ch06-custom-exception/ 独自例外クラス class XxxException extends Exception { } で意味のある例外を作れる
ch07-rethrow/ 再 throw (rethrow) catch した例外を呼び出し元へ投げ直せる (概念のみ)
ch08-validation-pattern/ バリデーション実践 入力検証で例外を活用する典型パターンを書ける

合計 8 chapter / 7 drill (ch07 は概念説明のみ・drill なし) / 所要 3〜4 時間 程度。try / catch の動きは概念より「書いて動かす」が早い。

進め方

  1. 各 chapter の slide.md を読む (3〜5 分)
  2. drill/ 配下の問題を順番に解く (ch07 は読むだけ)
  3. 採点: php scripts/grade.php topics/exception/<chapter>/drill/<drill>/

つまづきポイント

症状 多くの原因
Uncaught Exception でプログラムが止まる throw した例外を誰も catch していない
catch に入らない catch (DifferentException $e) のように違う型で捕まえようとしている。型階層を確認する
try の途中で throw した後の行が実行された throw の後にコードを書いている。throw 以降の行は実行されないことを再確認
$e->getMessage() が空 new Exception() の引数にメッセージを渡し忘れ
finally の中で return してしまった finally 内の returntry の戻り値より優先される。後始末以外は書かない
カスタム例外で Class XxxException not found extends Exception を書き忘れ、または use し忘れ

関連トピック

トピック 関係
class Exception もクラスの一種 (前提)
class-advanced extends Exception でカスタム例外を作る
function 不正な引数を throw で扱う設計が組み合わせ先
array-multi CSV パースなど、データ処理の失敗を例外で表現する

トピックを並列で参照する全体地図は TOPICS_INDEX.md にある。

案件 (dojo_map.tsv) での参照

topic_slug    chapter_dir
exception     topics/exception/ch01-try-catch
exception     topics/exception/ch02-throw
exception     topics/exception/ch06-custom-exception
...

slug exception で参照可。10-exception / exception どちらの path でもアクセスできる (シンボリックリンク)。

このレッスンの章

  1. ch01 ch01 — try / catch の基本
  2. ch02 ch02 — throw で例外を投げる
  3. ch03 ch03 — Exception::getMessage()
  4. ch04 ch04 — 複数の catch ブロック
  5. ch05 ch05 — finally 節
  6. ch06 ch06 — カスタム例外
  7. ch07 ch07 — 例外の再 throw (rethrow)
  8. ch08 ch08 — 入力検証で例外を活用する