あっぽログ
← 記事一覧に戻る

PHPの例外処理を完全理解する:try・catch・finally・カスタム例外を使いこなす

例外処理とは何か

プログラムを書いていると、「ファイルが見つからない」「データベースに接続できない」「受け取った値が想定外だった」といった問題が必ず発生します。こういった実行時エラーを適切に扱う仕組みが**例外処理(Exception Handling)**です。

PHPでは try / catch / finally ブロックを使って例外を捕捉・処理できます。例外処理を適切に使うことで、エラーが起きてもプログラムが突然終了するのを防ぎ、ユーザーへのわかりやすいメッセージ表示やログ記録が可能になります。


基本的な try / catch の書き方

最もシンプルな例外処理の構文は以下の通りです。

<?php

try {
    // 例外が発生するかもしれない処理
    $result = 10 / 0; // 実際にはこれはエラーにならないが、例として
    throw new Exception("ゼロ除算が発生しました");
} catch (Exception $e) {
    echo "エラーをキャッチしました: " . $e->getMessage();
}

throw で例外を投げ、catch で受け取ります。Exception は PHPに組み込まれた基底例外クラスです。

$e のメソッドには以下のものがあります:

メソッド説明
getMessage()エラーメッセージを取得
getCode()エラーコードを取得
getFile()発生したファイルを取得
getLine()発生した行番号を取得
getTrace()スタックトレースを取得

finally ブロックを使う

finally ブロックは、例外が発生したかどうかに関わらず必ず実行される処理を書く場所です。データベース接続やファイルのクローズ処理など、後片付けに使うのが典型的なパターンです。

<?php

function readFile(string $path): string
{
    $handle = fopen($path, 'r');

    if ($handle === false) {
        throw new RuntimeException("ファイルを開けませんでした: {$path}");
    }

    try {
        $content = fread($handle, filesize($path));
        return $content;
    } catch (RuntimeException $e) {
        echo "読み取り中にエラー: " . $e->getMessage();
        return '';
    } finally {
        // 例外があってもなくても必ずファイルを閉じる
        fclose($handle);
        echo "ファイルをクローズしました\n";
    }
}

finally はリソースの解放漏れを防ぐために非常に重要なブロックです。


複数の例外を catch する

catch ブロックは複数書くことができ、例外の種類に応じて異なる処理を行えます。また、PHP 8.0 以降では | を使って1つの catch で複数の例外型をまとめてキャッチできます。

<?php

function connectDatabase(string $host): void
{
    if (empty($host)) {
        throw new InvalidArgumentException("ホスト名が空です");
    }

    if ($host === 'unreachable') {
        throw new RuntimeException("データベースに接続できません");
    }

    echo "接続成功\n";
}

try {
    connectDatabase('unreachable');
} catch (InvalidArgumentException $e) {
    // 引数が不正な場合
    echo "引数エラー: " . $e->getMessage() . "\n";
} catch (RuntimeException $e) {
    // 実行時エラーの場合
    echo "接続エラー: " . $e->getMessage() . "\n";
} catch (Exception $e) {
    // その他すべての例外
    echo "予期しないエラー: " . $e->getMessage() . "\n";
}

PHP 8.0 以降のまとめキャッチ:

try {
    connectDatabase('');
} catch (InvalidArgumentException | RuntimeException $e) {
    // 両方の例外を同じ処理でまとめる
    echo "エラーが発生しました: " . $e->getMessage() . "\n";
}

カスタム例外クラスを作る

実際のアプリケーション開発では、独自の例外クラスを定義するのがベストプラクティスです。Exception クラスを継承するだけで簡単に作れます。

<?php

// 基底カスタム例外
class AppException extends RuntimeException {}

// より具体的な例外クラス
class UserNotFoundException extends AppException
{
    public function __construct(int $userId)
    {
        parent::__construct("ID:{$userId} のユーザーが見つかりません", 404);
    }
}

class ValidationException extends AppException
{
    private array $errors;

    public function __construct(array $errors)
    {
        parent::__construct("バリデーションに失敗しました", 422);
        $this->errors = $errors;
    }

    public function getErrors(): array
    {
        return $this->errors;
    }
}

// 使用例
function findUser(int $id): array
{
    $users = [1 => ['name' => '田中'], 2 => ['name' => '鈴木']];

    if (!isset($users[$id])) {
        throw new UserNotFoundException($id);
    }

    return $users[$id];
}

try {
    $user = findUser(99);
} catch (UserNotFoundException $e) {
    echo $e->getMessage() . "\n";       // "ID:99 のユーザーが見つかりません"
    echo "コード: " . $e->getCode();    // "コード: 404"
}

カスタム例外にすることで、catch ブロックで種類を絞り込みやすくなり、エラーの意味がコードから伝わりやすくなります。


例外処理のベストプラクティス

何でも catch しない

Exception ですべてを catch してしまうと、バグが隠れてしまうことがあります。想定している例外だけを catch するのが原則です。

エラーログを残す

本番環境では例外の内容をログに記録しておくと、問題の調査がしやすくなります。

catch (RuntimeException $e) {
    error_log($e->getMessage() . " in " . $e->getFile() . ":" . $e->getLine());
    // ユーザーへは汎用メッセージを表示
    echo "システムエラーが発生しました。しばらくお待ちください。";
}

例外は「例外的な状況」に使う

例外は通常フローの制御(if の代わりなど)には使わず、本当に予期しない・回復困難な状況にのみ使いましょう。


まとめ

PHPの例外処理を整理すると次のようになります:

  • try ブロック:例外が起きるかもしれない処理を書く
  • catch ブロック:例外を種類ごとに捕まえて処理する
  • finally ブロック:例外の有無にかかわらず必ず実行したい処理を書く
  • カスタム例外クラスで意味のあるエラー設計ができる

例外処理を正しく使うことで、堅牢で保守しやすいPHPアプリケーションが作れるようになります。ぜひ自分のプロジェクトで実践してみてください。

← 記事一覧に戻る