目次
全体像(例外の役割と境界)
例外は「通常フローでは回復できない状態」を呼び出し元へ即時に伝播させ、
適切な境界でロールバック・ログ・ユーザー向けレスポンスに変換するための仕組みです。
PHP 7以降はThrowable
が階層の最上位になり、その配下に Exception
(ユーザーランド・ライブラリ例外)と Error
(型エラー、パース時の致命的エラーなど)が並びます(Throwable, Exception, Error)。
「どの層で握るか(catchするか)」はアプリ境界の設計そのもので、
Webならミドルウェア/フロントコントローラ層、CLIならアプリのエントリポイントが典型です。
1. 基本:try
/ catch
/ finally
とスローの要点
1.1 基本の骨格
try {
// 例:DB書き込み、外部API呼び出し…
$id = $repo->create($payload);
echo "created: {$id}";
} catch (\DomainException $e) {
// 業務ルール違反(ユーザー入力の矛盾など)
http_response_code(400);
echo "Bad Request: {$e->getMessage()}";
} catch (\Throwable $e) {
// 想定外:ログして汎用メッセージ
error_log($e);
http_response_code(500);
echo "Internal Server Error";
} finally {
// ここは常に実行(コネクション解放・一時ファイル削除など)
$locker->release();
}
try/catch/finally
の構文は公式にまとまっています(try/catch, finally)。finally
は「成功・失敗を問わず走るクリーンアップ」の専用。リソース管理の最後の砦に。
1.2 スローの基本と再スロー
function storeUser(array $input): int {
if (!filter_var($input['email'] ?? '', FILTER_VALIDATE_EMAIL)) {
throw new \DomainException('メール形式が不正です');
}
// 例:throwが式として使える
$value = $input['key'] ?? throw new \InvalidArgumentException('キーが必要です');
try {
return $this->gateway->insert($input);
} catch (\PDOException $e) {
// インフラ例外 → ドメインに近い例外へ変換して上へ伝える
throw new \RuntimeException('永続化に失敗', previous: $e);
}
}
- 例外に元例外を連鎖(
previous
)させると解析が楽になります(Exception::getPrevious)。 - PHP 8以降は
throw
が式として使えます(条件演算子内など)。
2. 応用:複数 catch
、型階層、再試行、トランザクション連携
2.1 複数 catch
とユニオンキャッチ
try {
$client->postJson($url, $body);
} catch (\InvalidArgumentException|\LengthException $e) {
// 入力バリデーション系をまとめて 400 に
respondBadRequest($e->getMessage());
} catch (\RuntimeException $e) {
// 実行時エラーは 500 に
respondServerError('処理に失敗しました');
}
- PHP 8+ はユニオン型の
catch
が使えます(例外の処理)。
2.2 リトライ戦略(再試行)
function withRetry(callable $op, int $max = 3, int $backoffMs = 200) {
for ($i = 1; $i <= $max; $i++) {
try { return $op(); }
catch (\Throwable $e) {
if ($i === $max) throw $e;
usleep($backoffMs * 1000);
}
}
}
- ネットワーク系は一時的失敗があり得るので、上位で再試行を包むのが定石。
- ただし非冪等操作(重複実行で副作用が累積)には要注意。
2.3 トランザクションと例外
$db->beginTransaction();
try {
$orderId = $orders->create($payload);
$ledger->record($orderId);
$db->commit();
} catch (\Throwable $e) {
$db->rollBack();
throw $e;
}
- 例外が「ロールバックのトリガ」になるのは実務の定番。
- DB例外は
PDOException
(PDOException)で受け止め、再スローで層をまたぐのがよくある変換パターン。
3. カスタム例外設計:ドメイン・アプリ・インフラの分離
3.1 例外の命名と粒度
- ドメイン層:
DomainException
を基底に、業務ルール違反を表現(例:InsufficientBalance
)。 - アプリ層:ユースケースの前提違反を
InvalidOperation
等で。 - インフラ層:
StorageUnavailable
,ThirdPartyTimeout
など「依存外部要因」。
namespace App\Domain;
class InsufficientBalance extends \DomainException {
public function __construct(public readonly int $shortage) {
parent::__construct("残高不足: {$shortage}円不足");
}
}
- 例外にドメイン情報(不足額など)をプロパティで持たせると、メッセージ生成や監視で再利用できます。
3.2 変換(Wrapping)と境界でのマッピング
- インフラ例外 → アプリ/ドメイン例外へ変換
- Web境界 → HTTPステータス/エラーレスポンスへマッピング
- CLI境界 → 終了コードと標準エラー出力へ
try {
$useCase->execute($cmd);
} catch (\App\Domain\InsufficientBalance $e) {
respondJson(400, ['code' => 'INSUFFICIENT_BALANCE', 'shortage' => $e->shortage]);
} catch (\App\Infra\ThirdPartyTimeout $e) {
respondJson(504, ['code' => 'GATEWAY_TIMEOUT']);
}
4. SPL例外・標準例外の使い分け
- 代表例:
InvalidArgumentException
,LengthException
,OutOfBoundsException
,RuntimeException
(SPL例外の一覧)。 - 入力がおかしい→
InvalidArgumentException
- 状態がおかしい(前提が崩れた)→
LogicException
系 - 実行時に起きた避けにくい問題→
RuntimeException
系 - ライブラリ側はSPL例外で十分なことが多く、アプリ側で自前例外に包んで扱いやすい語彙へ寄せるのが実務的。
5. 例外とエラーの違い・ハンドラ設計
Error
は型ミスマッチ・未定義関数などプログラミングエラーで、基本は修正対象(Error)。- アプリのエントリポイントでは
Throwable
全面キャッチで落ちないようにしつつ、Error
はログ+失敗レスポンスに留め、回復を試みないのが原則。 - グローバルのハンドラは
set_exception_handler()
(公式)で定義可能。ただしフレームワークのハンドラに委ねるのが一般的。
6. ロギング・観測性:PSR-3/構造化ログ/相関ID
- PSR-3 ロガー(
LoggerInterface
)で例外・メタ情報を構造化して出力(context
にexception
を渡す)。 - 相関ID(correlationId) を1リクエストに1つ発行してログに含めると、分散環境でも追跡容易。
- 例外はスタックトレース量が多いため、メッセージはユーザー向けとログ向けを分離(UXとセキュリティのため)。
7. 代案:戻り値エラー・Result
型・ガード節
7.1 戻り値でのエラー表現(慎重に)
// 戻り値で成功/失敗(PHPらしいが、取りこぼしやすい)
[$ok, $valueOrError] = $service->tryFetch($id);
if (!$ok) { return respondNotFound(); }
- 取りこぼしリスクが高く、深いネストになりがち。本当に例外が重い箇所に限定を。
7.2 Result
風ラッパ(成功/失敗を型で表現)
final class Result {
private function __construct(private ?mixed $ok, private ?\Throwable $err) {}
public static function ok(mixed $v): self { return new self($v, null); }
public static function err(\Throwable $e): self { return new self(null, $e); }
public function unwrap(): mixed { if ($this->err) throw $this->err; return $this->ok; }
}
- 例外を捕捉・保持して後段で処理したい場合に有効。パイプライン処理で便利。
7.3 ガード節とバリデーション
- バリデーションは例外ではなく「早期return」で返すほうが読みやすい場面も多い。
- 「本当に異常か?」を見極めて、制御フローを例外で“組まない”のも上級者の判断。
8. パフォーマンスの実際:計測と指針
- 例外スローは高コスト(トレース構築・オブジェクト生成)。
- 通常フローで例外を使わない(例:ループ内の分岐に例外を多用しない)。
- コストが支配的なのは I/O であり、例外の有無よりDB/ネットワークの設計が遥かに影響大。
- 実測は
hrtime()
(公式)+代表ケースでベンチを取る。例外をベンチ対象にしないのが本筋(異常系は頻発させない)。
9. テスト戦略:例外の期待・契約テスト
- PHPUnitなら
expectException()
/expectExceptionMessage()
で契約を明示。 - 層をまたぐ変換(
PDOException
→RuntimeException
)はユニットテストで固定。 - エンドツーエンド(E2E)ではHTTPステータス/エラーコードが一致することを検証。
public function testInsufficientBalance(): void {
$this->expectException(\App\Domain\InsufficientBalance::class);
$this->service->withdraw($userId, 999999);
}
10. アンチパターン集(やりがちな落とし穴)
- なんでも
catch (Throwable)
して黙殺:バグを隠す最悪パターン。ログ+可観測性を。 finally
で戻り値を上書き:可読性が落ちる。finally
はクリーンアップ専用に。- 例外メッセージに機密情報:SQLやトークンを露出しない。ユーザー向けとログ向けを分離。
Error
は境界で捕捉し、ログ記録後にクリーンにプロセスを終了させるのが原則- 例外を制御フローの代用に濫用:通常分岐で書けるものは書く。異常時のみ例外。
11. 現場向けチェックリスト
- 例外の境界(握る場所)はフロントコントローラ/ミドルウェアに集約したか
- カスタム例外階層で語彙を整理したか(Domain/App/Infra)
- 再スロー時に
previous
を連鎖させているか - ロールバックは
try/catch
の近傍で必ず行うか - PSR-3 で構造化ログ+相関IDを残しているか
- ユーザー向けメッセージから機密情報を排除したか
- 例外を濫用していないか(通常分岐で書ける箇所の見直し)
参考リンク(文中で参照した主な公式)
- 例外の基本・
try/catch/finally
: PHP 例外 Throwable
/Exception
/Error
: Throwable, Exception, ErrorPDOException
: PDOException- 高分解能タイマ: hrtime()
- グローバル例外ハンドラ: set_exception_handler()
まとめ
- 例外は設計の道具:境界で握り、適切に変換し、ユーザー文脈へマッピング。
- 階層設計と語彙の整備で、運用・監視・テストが一気に楽になる。
- 性能は I/O と設計が支配:例外のコストは異常時だけ。通常フローで使わない。
- “握る場所を決める” ことが、現場品質(可用性・可観測性)を左右する。
あわせて読みたい ~徹底解説シリーズ~
ぷろぐらの森


PHPの echo と if を完全攻略:エンジニアのための徹底解説 – ぷろぐらの森
PHPのechoとif文を徹底解説。基本構文から応用的な使い方、代替手法、処理速度の比較、現場での使い分けまで網羅したエンジニア向け完全攻略ガイド。
ぷろぐらの森


PHPの for / foreach 完全攻略:エンジニアのための徹底解説 – ぷろぐらの森
PHPのfor文とforeach文を徹底解説。基本構文から応用的な使い方、代替手段の紹介、処理速度の比較、現場での使い分けまで。上級エンジニア向けの完全攻略ガイド。
ぷろぐらの森


一次元・多次元・連想配列を完全攻略:エンジニア向け徹底解説 – ぷろぐらの森
PHPの一次元配列・多次元配列・連想配列を徹底解説。基本から応用、代替データ構造の紹介、処理速度比較まで、エンジニア向けに完全攻略します。
おすすめ本を紹介!
ぷろぐらの森


【2025年版】おすすめプログラミング本10選|初心者〜中級者編 – ぷろぐらの森
プログラミング初心者から中級者までにおすすめの本を厳選紹介。PHP・Laravel・HTML/CSS・JavaScriptまで、学習のステップに応じた本を選べば効率よくスキルアップできます…
コメント