第11章 エラー処理と例外

エラーレベル

PHPには、エラーレベルという概念があります。これは、エラーの重大性を示すもので、エラーハンドリングの際に非常に重要です。以下にエラーレベルの一部を示します。

// E_NOTICE レベルのエラー。変数が未定義など、実行には影響のないエラー
$undefined_variable; // E_NOTICE: Undefined variable: undefined_variable

// E_WARNING レベルのエラー。存在しないファイルをincludeするなど、実行に影響のあるエラー
include 'non_existent_file.php'; // E_WARNING: include(non_existent_file.php): failed to open stream: No such file or directory


PHPには下記のようなエラーレベルが存在します

E_ERROR: 致命的なランタイムエラー。これは実行時間エラーで、これが発生するとスクリプトの実行が停止します。
echo $undefined_variable;  // 未定義の変数を使用した場合、E_ERRORが発生します


E_WARNING: 実行時の警告(非致命的なエラー)。スクリプトの実行は停止しません。
echo 10 / 0;  // 0で割るとE_WARNINGが発生しますが、スクリプトの実行は続行されます


E_PARSE: コンパイル時のパースエラー。パースエラーは解析エラーとも呼ばれ、スクリプトが正しく構文解析できない場合に発生します。
echo "Hello  // クォートが閉じられていないため、E_PARSEエラーが発生します


E_NOTICE: 実行時の通知。これはエラーではなく、スクリプトの実行に問題がないか確認するためのものです。
$array = array('a' => 'apple', 'b' => 'banana');
echo $array['c'];  // 'c'というキーは存在しないため、E_NOTICEが発生します


E_CORE_ERROR: PHPの初期起動(スタートアップ)中に発生したエラー。


E_COMPILE_ERROR: zendのコンパイル時エラー。


E_STRICT: PHPコードの将来の互換性に問題がある場合に警告するためのエラー。
class MyClass {
    function myMethod() {
        echo 'Hello, world!';
    }
}

// 非静的メソッドの静的呼び出し(E_STRICTエラー)
MyClass::myMethod();


E_RECOVERABLE_ERROR: 通常の実行を停止するが、エラーハンドラによって回復可能なエラーを示す。
function myFunction(array $param) {
    // ...
}

// 配列ではない引数を渡す(E_RECOVERABLE_ERROR)
myFunction('not an array');


E_DEPRECATED: 使用が推奨されない機能がコードで使われている場合に警告するエラー
// PHP7.2.0で非推奨となった関数を使用(E_DEPRECATEDエラー)
$func = create_function('', 'echo "Hello, world!";');
$func();


E_USER_ERROR: trigger_error()関数を使用してユーザーが指定したエラー。
if ($condition == false) {
    trigger_error("条件がfalseです", E_USER_ERROR);
}


例外処理

例外処理は、エラーハンドリングの一形態で、try-catch文を用いてエラー(例外)を捕捉し、対応する処理を行います。以下に例外処理の一例を示します。

try {
    // 存在しないファイルを開こうとする(例外が発生)
    $file = fopen('non_existent_file.txt', 'r');
} catch (Exception $e) {
    // 例外が捕捉されたら、エラーメッセージを表示
    echo "例外が発生しました:{$e->getMessage()}";
} finally {
    // 例外の有無に関わらず、必ず実行されます
    echo "この部分は必ず実行されます。";
}

このプログラムを実行すると、例外が発生します。その例外は catch ブロックにより捕捉され、エラーメッセージが表示されます。その後、finally ブロックの内容が実行されます。

例外処理の概念を理解することで、エラーが発生したときのコードの振る舞いをコントロールし、更にはエラーメッセージを利用者に適切に伝えることができます。これは大規模なアプリケーション開発において、非常に重要な技術となります。

  • tryブロック:例外が発生する可能性のあるコードをこの中に書きます。例外が発生した場合、直ちにその後のコードの実行を停止し、最初の対応する catch ブロックに移動します。
  • catchブロック:特定の例外を捕捉して処理します。対応する例外の種類を指定し、その例外が try ブロックで発生した場合に実行されます。
  • finallyブロック:これはオプションで、例外の有無にかかわらず必ず実行されるコードを記述します。


例外の種類

基本的な例外はExceptionクラスを通じて提供されています。しかし、様々な種類の特定のエラー状況を捕捉するために、いくつかの例外クラスが提供されています。

以下にいくつか挙げます

  • LogicException: プログラムのロジックエラーを表すための例外。これは、実行前に検出可能なエラーを表す基底クラスです。
  • RuntimeException: 実行時にのみ検出可能なエラーを表す基底クラスです。
  • ErrorException: エラーから例外を作成するための例外。これは、PHPのエラーメッセージを例外として捕捉するために使用されます。
  • OutOfBoundsException: 有効な範囲を超えたときにスローされる例外です。
  • UnderflowException: スタックやキューが空であるなど、何かが空であるときにスローされる例外です。

以下は例外クラスを使ったコード例です。

try {
    // 例外が発生する可能性のあるコード
    throw new Exception("これは一般的な例外です");
} catch (Exception $e) {
    echo $e->getMessage();  // "これは一般的な例外です"と出力
}

try {
    // 例外が発生する可能性のあるコード
    throw new OutOfBoundsException("これは範囲外の例外です");
} catch (OutOfBoundsException $e) {
    echo $e->getMessage();  // "これは範囲外の例外です"と出力
}

例外は、エラーや問題を捕捉してそれらを適切に処理するための強力なツールです。これらの例外クラスを使用することで、より具体的な問題を表現し、より詳細なエラーメッセージを提供することができます。

ただし、これらの例外クラスは基本的なもので、プロジェクトの要件に応じて独自の例外クラスを作成することも一般的です。これにより、より具体的なエラー状況を表現し、エラーハンドリングをより細かく制御することができます。


独自例外

PHPでは、ユーザー定義の例外を作成することが可能です。これは特定の用途や状況に対して専用の例外を作成する際に非常に便利です。

ユーザー定義の例外を作成するには、まず Exception クラスを継承したクラスを作成します。このクラスは必要に応じて追加のプロパティやメソッドを持つことができます。

以下に、年齢が不適切な値(例えば負の数)である場合にスローされる独自の例外を作成する例を示します。

class InvalidAgeException extends Exception {
    public function __construct($message, $code = 0, Exception $previous = null) {
        parent::__construct($message, $code, $previous);
    }

    // カスタムエラーメッセージのメソッド
    public function customFunction() {
        echo "InvalidAgeExceptionが発生しました: {$this->message}\n";
    }
}

class Person {
    private $age;

    public function __construct($age) {
        if ($age < 0) {
            throw new InvalidAgeException("年齢は0以上でなければなりません。入力された年齢: $age");
        }

        $this->age = $age;
    }
}

try {
    $person = new Person(-5); // この行でInvalidAgeExceptionがスローされます
} catch (InvalidAgeException $e) {
    echo $e->customFunction(); // カスタムエラーメッセージを表示
}

このプログラムでは、Personクラスのコンストラクタ内で、引数として与えられた年齢が0未満である場合にInvalidAgeExceptionをスローしています。そしてtry-catchブロックでその例外を捕捉し、カスタムエラーメッセージを表示しています。


独自例外のメリット

  1. 詳細なエラー情報を提供可能:標準の例外クラスだけでは具体的なエラー情報を提供できない場合、独自の例外クラスを作成して具体的なエラー情報を含めることができます。これにより、デバッグ作業が容易になります。
  2. より厳密なエラーハンドリング:独自の例外クラスを作成することで、特定のタイプのエラーだけを対象としたエラーハンドリングが可能になります。例えば、特定の独自例外がスローされた場合だけ特別な処理を行いたい場合などに有用です。
  3. コードの読みやすさと保守性の向上:例外クラスの名前やエラーメッセージにビジネスロジックに関連した情報を反映させることができます。これにより、コードの読みやすさや保守性が向上します。


独自例外のデメリット

  1. コードの複雑性が増す:独自の例外クラスを作成することで、コードベース全体が複雑になる可能性があります。例外クラスを適切に管理し、不必要に多くの例外クラスを作成しないよう注意が必要です。
  2. 開発とメンテナンスのコストが増大:独自の例外クラスを作成、使用するにはその例外クラスを開発し、テストし、メンテナンスするための時間と労力が必要になります。


練習問題1.

以下のコードを考えてください。

ini_set('display_errors', '1');
ini_set('error_reporting', E_ALL);

echo $undefined_variable;

上記コードを実行した時に何が起こるか説明してください。

error_reporting の設定を E_ALL & ~E_NOTICE に変更したら何が起こるか説明してください。

E_ALL & ~E_NOTICE という設定の意味を説明してください。


練習問題2.

以下のコードを考えてください。

function divide($dividend, $divisor) {
    if ($divisor == 0) {
        throw new Exception("除数は0にできません");
    }

    return $dividend / $divisor;
}

try {
    echo divide(5, 0);
} catch (Exception $e) {
    // ここで例外をキャッチします。何を行うべきですか?
}

上記のコードで// ここで例外をキャッチします。何を行うべきですか?の部分に何を書くべきか説明してください。


練習問題3.

InsufficientFundsExceptionという独自の例外を作成してください。この例外は、銀行口座の残高が引き出し金額より少ないときにスローされるものとします。

その上で、withdrawという関数を作成してください。この関数は、口座の残高と引き出し金額をパラメータとし、残高が十分な場合には新しい残高を返すものとします。残高が不足している場合は、先ほど作成したInsufficientFundsExceptionをスローします。


7