【PHP】例外処理(try-catch-finally)の使い方|カスタム例外クラスの作り方も解説
PHP でプログラムを書いていると、ファイル読み込みや外部 API 呼び出しなど「失敗する可能性のある処理」を扱う場面が必ずあります。こうした処理を安全に扱うための仕組みが例外(Exception)であり、try・catch・finally を使った例外処理です。本記事では基本的な構文から実務でよく使うパターンまでを整理します。
動作の細部は PHP のバージョンによって異なる場合があります。コード例は執筆時点で PHP 8.x を主な参照先とし、手元の環境に合わせて PHP 公式マニュアルの例外処理の節 を並行して参照することをおすすめします。
はじめに
「外部 API を呼んだら接続タイムアウトになった」「ファイルを開こうとしたら存在しなかった」——こうしたエラーは事前にすべてを防ぐことはできません。エラーが発生したとき、プログラムが突然停止するのではなく「想定済みの異常として処理を引き継ぐ」ために例外処理を使います。
PHP の例外処理は try・catch・finally の 3 ブロックで構成されます。
tryブロックに「例外が発生し得る処理」を書くcatchブロックに「例外が発生したときの処理」を書くfinallyブロックに「例外の有無にかかわらず必ず実行したい処理」を書く
この記事では次の範囲を扱います。
try・catch・finallyの基本構文- 複数の例外クラスを使い分ける方法
- 独自例外クラスの定義
finallyの使いどころと注意点- よくある失敗例と対処
背景:PHP の例外クラス階層
PHP の例外は Throwable インターフェースを頂点とした継承ツリーになっています。
Throwable
├── Error(PHP エンジンレベルのエラー)
│ ├── TypeError
│ ├── ValueError
│ └── ...
└── Exception(アプリケーションレベルの例外)
├── RuntimeException
├── InvalidArgumentException
├── LogicException
└── ...
catch で捕捉できるのは Throwable を実装したオブジェクトです。通常のアプリケーションコードでは Exception またはそのサブクラスを扱います。Error は PHP エンジン自身が投げるエラーで、TypeError(型の不一致)などが該当します。次の「実装例」では、この階層を踏まえた具体的なコードを示します。
実装例
基本構文
最小限の try・catch の例です。
try {
$result = riskyOperation();
} catch (Exception $e) {
echo 'エラーが発生しました: ' . $e->getMessage();
}
throw で意図的に例外を発生させることもできます。
function divide(int $a, int $b): float
{
if ($b === 0) {
throw new InvalidArgumentException('除数に 0 は指定できません。');
}
return $a / $b;
}
try {
$result = divide(10, 0);
} catch (InvalidArgumentException $e) {
echo $e->getMessage(); // 除数に 0 は指定できません。
}
例外オブジェクトからは次のメソッドで情報を取得できます。
| メソッド | 内容 |
|---|---|
getMessage() | エラーメッセージ |
getCode() | エラーコード(throw new Exception('msg', 100) で指定) |
getFile() | 例外が発生したファイルパス |
getLine() | 例外が発生した行番号 |
getPrevious() | 前の例外(チェーンしている場合) |
複数の catch ブロック
例外の種類ごとに処理を分けたい場合は、catch を複数並べます。上から順に評価されるため、より具体的なクラスを先に書くのが基本です。
try {
$data = fetchDataFromApi($url);
} catch (ConnectionException $e) {
// 接続失敗:リトライやフォールバックを検討
logger()->error('API 接続エラー', ['message' => $e->getMessage()]);
} catch (InvalidArgumentException $e) {
// 引数誤り:呼び出し側のバグの可能性が高い
logger()->error('引数エラー', ['message' => $e->getMessage()]);
} catch (Exception $e) {
// その他の例外
logger()->error('予期しないエラー', ['message' => $e->getMessage()]);
}
PHP 8.0 以降では、1 つの catch ブロックで複数の例外型を | でまとめられます(Union Catch)。
catch (ConnectionException | TimeoutException $e) {
// 接続系エラーを一括処理
}
finally ブロック
finally に書いた処理は、例外の有無にかかわらず try / catch の後に必ず実行されます。ファイルハンドルやデータベース接続のクローズなど、「後始末」に向いています。
$file = fopen('data.txt', 'r');
try {
$content = processFile($file);
} catch (RuntimeException $e) {
logger()->error($e->getMessage());
} finally {
fclose($file); // 例外が発生しても必ず閉じる
}
try の中で return しても finally は実行されます。
function readConfig(string $path): array
{
$file = fopen($path, 'r');
try {
return parseFile($file); // return しても finally は動く
} finally {
fclose($file);
}
}
独自例外クラスの定義
アプリケーション固有のエラーを表すために、Exception を継承した独自クラスを定義します。
class UserNotFoundException extends RuntimeException
{
public function __construct(int $userId)
{
parent::__construct("ユーザー ID {$userId} が見つかりません。", 404);
}
}
function findUser(int $id): User
{
$user = User::find($id);
if ($user === null) {
throw new UserNotFoundException($id);
}
return $user;
}
独自例外クラスを使うと、catch で型を指定して捕捉できるため、処理の分岐が明確になります。
// 以下は Laravel フレームワークを使用した例
try {
$user = findUser(999);
} catch (UserNotFoundException $e) {
// 404 相当の対処
return response()->json(['error' => $e->getMessage()], $e->getCode());
}
例外チェーン(原因の保持)
例外を別の例外に包んで再スローするときは、第 3 引数に元の例外を渡します。こうすると getPrevious() で原因を遒れます。
// PDO(PHP Data Objects)はデータベース操作の標準的な PHP 拡張
try {
$pdo->query($sql);
} catch (PDOException $e) {
throw new DatabaseException('クエリに失敗しました。', 0, $e);
}
実装時の注意点
catch する例外は具体的に絞る
catch (Exception $e) で何でも捕捉すると、意図しない例外まで握りつぶす恐れがあります。捕捉する例外クラスは処理の意図に合わせて具体的に指定します。
finally の中での return や throw には注意する
finally ブロックで return または throw すると、try や catch ブロックの return / throw を上書きします。finally は原則として後始末のみに使い、値を返したり例外を投げたりするのは避けた方が無難です。
例外メッセージにユーザー入力をそのまま含めない
ログや外部レスポンスに例外メッセージをそのまま出力すると、内部情報の漏洎につながる場合があります。ユーザーへの表示用メッセージと開発者向けログは分けて管理します。
失敗例:catch で例外を握りつぶして原因が追えなくなる
// 問題のある書き方
try {
$result = expensiveOperation();
} catch (Exception $e) {
// 何もしない(握りつぶし)
}
例外を捕捉しても何もしないと、処理失敗の事実が隠れ、後からデバッグできなくなります。少なくともログに記録するか、上位に再スローします。
// 改善例
try {
$result = expensiveOperation();
} catch (RuntimeException $e) {
logger()->error('処理に失敗しました。', ['exception' => $e]);
throw $e; // 上位で判断させる場合は再スロー
}
学び:例外処理で設計が整理されること
try・catch・finally を正しく使うと、次の点でコードが整理されます。
正常系と異常系の分離
メインの処理を try に集め、エラー対応を catch に分けることで、正常系のフローが読みやすくなります。
後始末の確実な実行
finally を使うことで、ファイルやネットワーク接続のクローズを例外の有無にかかわらず確実に行えます。
エラーの種類に応じた対処
独自例外クラスと複数の catch を組み合わせることで、エラーの種類に応じた処理(リトライ・ログ・レスポンスコードの変更など)を明確に書けます。
まとめ
tryに例外が発生しうる処理、catchに例外への対処、finallyに後始末を書く。catchは具体的な例外クラスを指定し、握りつぶしを避ける。Exceptionを継承した独自クラスを使うと、エラーの種類ごとに処理を分けやすくなる。finallyの中でのreturn/throwはtry/catchの結果を上書きするため注意が必要である。
次に試せること
- 自分のプロジェクトで「エラーが返ったら何もしない」箇所を探し、ログ記録または再スローに改善する。
- ドメイン固有の操作(ユーザー検索・外部 API 呼び出しなど)に対して独自例外クラスを 1 つ定義してみる。
- PHP 公式マニュアルの例外処理 を読み、
set_exception_handlerによるグローバルな例外ハンドラの設定を確認する。



