【PHP入門】例外処理の基本 try・catch・finally・throwを理解しよう

プログラムでは、ファイルが見つからない、データベースへ接続できない、不正な値が渡された、といった問題が起こります。PHPの例外処理を使うと、問題を検知し、利用者向けの表示と開発者向けの記録を分けられます。

この記事では、throwtrycatchfinallyの役割を、割り算とファイル保存の例で学びます。

エラーと例外の違い

PHPには構文エラー、警告、例外など複数の問題通知があります。セミコロンや括弧の書き間違いは、先によくあるPHPエラーで確認してください。

例外は、プログラムからthrowでき、catchで受け止められるオブジェクトです。適切に処理すれば、エラー画面をそのまま利用者へ見せず、安全な案内へ切り替えられます。

throwで例外を発生させる

処理を続けられない条件を見つけたら、throwで例外を投げます。

<?php
function divide(float $left, float $right): float
{
    if ($right === 0.0) {
        throw new InvalidArgumentException("0では割れません");
    }

    return $left / $right;
}

例外が投げられると、その位置より後の処理は実行されず、対応するcatchが探されます。

tryとcatchで例外を処理する

<?php
function divide(float $left, float $right): float
{
    if ($right === 0.0) {
        throw new InvalidArgumentException("0では割れません");
    }

    return $left / $right;
}

try {
    $answer = divide(10, 0);
    echo $answer;
} catch (InvalidArgumentException $exception) {
    echo "計算できませんでした: " . $exception->getMessage();
}

echo PHP_EOL . "処理を続けます";

tryには例外が起こる可能性のある処理を書き、catchには例外が起きた場合の処理を書きます。上の例では、例外を処理したあともプログラムを続けられます。

finallyは成功・失敗にかかわらず実行される

finallyは、例外が発生した場合も、発生しなかった場合も実行されます。ファイルや接続など、最後に必ず片付けたい処理に使います。

<?php
$handle = null;

try {
    $handle = fopen(__DIR__ . "/sample.txt", "rb");

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

    echo stream_get_contents($handle);
} catch (RuntimeException $exception) {
    error_log($exception->getMessage());
    echo "ファイルを読み込めませんでした";
} finally {
    if (is_resource($handle)) {
        fclose($handle);
    }
}

複数の例外を分けて処理する

例外の種類によって対応を変えたい場合は、catchを複数書きます。上から順に一致する型が選ばれるため、具体的な例外を先に、広い例外を後に書きます。

<?php
try {
    throw new InvalidArgumentException("入力値が不正です");
} catch (InvalidArgumentException $exception) {
    echo "入力内容を確認してください";
} catch (RuntimeException $exception) {
    echo "一時的な問題が発生しました";
} catch (Throwable $exception) {
    echo "予期しない問題が発生しました";
}

Throwableは、ExceptionとPHPの多くのErrorに共通するインターフェースです。最上位で予期しない問題を記録するときに使えますが、すべてを握りつぶさないようにしてください。

独自例外で問題の意味を表す

アプリ独自の例外クラスを作ると、何が問題だったかを型で表現できます。

<?php
class InsufficientStockException extends RuntimeException
{
}

function purchase(int $stock, int $quantity): int
{
    if ($quantity < 1) {
        throw new InvalidArgumentException("購入数は1以上にしてください");
    }

    if ($stock < $quantity) {
        throw new InsufficientStockException("在庫が不足しています");
    }

    return $stock - $quantity;
}

try {
    echo purchase(3, 5);
} catch (InsufficientStockException $exception) {
    echo "在庫数を確認してください";
}

利用者への表示とログを分ける

本番環境で例外メッセージやスタックトレースをそのまま画面へ表示すると、ファイルパスや設定情報が漏れる可能性があります。

<?php
try {
    throw new RuntimeException("Database host: internal-db.example");
} catch (Throwable $exception) {
    // 開発者向け:詳細をログへ残す
    error_log((string) $exception);

    // 利用者向け:内部情報を含まない表示
    echo "現在処理を完了できません。時間をおいて再度お試しください。";
}

開発環境では問題を見つけるためerror_reporting(E_ALL)を有効にし、本番環境では画面表示を無効にしてログへ記録するのが基本です。

例外を使う場面

  • データベースや外部サービスへの接続に失敗した
  • 必要なファイルや設定を読み込めない
  • 関数が前提とする値を受け取れない
  • 在庫不足など、通常の戻り値では表しにくい失敗が起きた

入力欄の未記入のように、利用者が修正できる通常の検証エラーは、必ずしも例外にする必要はありません。戻り値やエラー配列で扱う方が読みやすい場合もあります。

よくある間違い

  • tryの範囲を広げすぎ、どの処理が失敗したか分からなくする
  • catch (Throwable)で受けたあと、ログを残さず無視する
  • 秘密情報を含む例外メッセージをそのまま画面へ表示する
  • finallyの中で再び例外を起こし、元の例外を分かりにくくする

練習問題

正の整数だけを受け取るcalculateSquare()関数を作ってください。0以下ならInvalidArgumentExceptionを投げ、呼び出し側でメッセージを表示します。

解答例を見る
<?php
function calculateSquare(int $number): int
{
    if ($number <= 0) {
        throw new InvalidArgumentException("正の整数を指定してください");
    }

    return $number ** 2;
}

try {
    echo calculateSquare(-3);
} catch (InvalidArgumentException $exception) {
    echo $exception->getMessage();
}

まとめ

  • throwで処理を続けられない問題を通知する
  • try / catchで例外を受け止める
  • finallyで成功・失敗にかかわらず必要な後処理を行う
  • 本番では詳細をログへ残し、画面には安全なメッセージを表示する

例外処理は、PDOとMySQLによるCRUDでも実際に使用します。

PHP学習ロードマップへ戻る

関連記事

コメント

この記事へのコメントはありません。