PHP - OOP - 7. 例外(Exceptionクラス)

ここからは例外( Exception )について学習します。例外( Exception )とはプログラムの予期せぬ事態を通知する仕組みです。例外とよく似た言葉にエラーがありますが、一般的に、PHPのエラーとは復旧することのできない致命的な問題を意味します。一方の例外は想定外の事態を意味しますが、適切に対処することで処理を継続することも可能となります。

例外の仕組み

PHPには例外を扱うためのクラスとして Exception クラスが用意されています。 Exception クラスのインスタンスを生成することで、プログラムの中で例外を生成できます。

$e = new Exception();

また Exception クラスには例外メッセージを引数にとるコンストラクタも用意されているので、次のように Exception インスタンスを生成できます。

$e = new Exception("Exctption message.");

生成した例外インスタンスは throw キーワードによって通知できます。

$e = new Exception("Exctption message.");
throw $e;

PHPでは例外を通知することを「例外をスローする」と言います。スローされた例外インスタンスは後で紹介する try - catch 構文によって処理できるようになっています。

まずは例外をスローする方法を確認しましょう。以前に作成している MyClass.php ファイルの myMethod メソッドを次のように修正します。

<?php
class MyClass
{
    private $myProperty;

    public function __construct($myProperty)
    {
        $this->myProperty = $myProperty;
    }

    public function myMethod($x)
    {
        if ($x === "") {
            $e = new Exception("Invalid argument.");
            throw $e;
        }
        echo $this->myProperty . " " . $x . PHP_EOL;
    }

    public function setMyProperty($myProperty)
    {
        $this->myProperty = $myProperty;
    }

    public function getMyProperty()
    {
        return $this->myProperty;
    }
}

ここでは myMethod の引数 $x"" から文字だった場合に Exception インスタンスをスローするように実装しています。メソッドの中で throw キーワードによって例外がスローされるとメソッドの処理はそこで終了し、メソッドの呼び出し元に例外がスローされるようになっています。

それでは実際に例外が発生する(スローされる)様子を見てみましょう。my_class_runner.php ファイルを次のように修正します。

<?php
require_once("MyClass.php");

$myClass = new MyClass("Hello");
$myClass->myMethod(""); #=> throw Exception

プログラムを実行すると $myClass->myMethod("") の呼び出しによって Exception インスタンスがスローされます。次のような結果が出力されるでしょう。

$ php my_class_runner.php

Fatal error: Uncaught Exception: Invalid argument. in /home/ec2-user/environment/MyClass.php:14
Stack trace:
#0 /home/ec2-user/environment/my_class_runner.php(5): MyClass->myMethod('')
#1 {main}
  thrown in /home/ec2-user/environment/MyClass.php on line 14

ここでは my_class_runner.php ファイルと MyClass.php ファイルの関係を整理しておきましょう。

my_class_runner.php ファイルの中で MyClass 型のインスタンスに対して myMethod メソッドを呼び出しています。このとき引数に "" 空文字を指定しているので、MyClass クラスの myMedhod の内部で例外(Exception 型のインスタンス)がスローされます。

スローされた例外は次節で紹介する try - catch 構文によって処理できるようになっています。

PHPプログラムの開発(0による除算)

それでは簡単な計算機クラス( SimpleCalc.php )を題材に例外について考えてみましょう。これまでに加算処理を行う add メソッドを実装しているので、ここでは新たに以下の3つのメソッドを追加して四則演算を完成させます。

  • subtract メソッド(減算)
  • multiplyメソッド(乗算)
  • divide メソッド(除算)
<?php
class SimpleCalc
{
    // 省略

    public function add($x)
    {
        $this->number = $this->number + $x;
    }

    public function subtract($x)
    {
        $this->number = $this->number - $x;
    }

    public function multiply($x)
    {
        $this->number = $this->number * $x;
    }

    public function divide($x)
    {
        $this->number = $this->number / $x;
    }

    public function show()
    {
        echo $this->number . PHP_EOL;
    }
}

ここでは除算を行う divide メソッドに注目してください。 divide メソッドの引数に 0 を指定すると 、 0 による除算が発生します。このような演算は行えないため、PHPは実行時に Warning メッセージを出力します。

実行プログラム( calc_runner.php )を以下のように修正してみましょう。

<?php
require_once("SimpleCalc.php");

$calc = new SimpleCalc();

$calc->add(10);
$calc->subtract(5);
$calc->multiply(10);
$calc->divide(0); // Warning!

$calc->show();

ここでは $calc->divide(0) と呼び出している部分で 0 による除算が発生し、戻り値には無限を表す定数 INF が返却されます。ターミナルからプログラムを実行してみましょう。

$ php calc_runner.php
Warning: Division by zero in /home/ec2-user/environment/SimpleCalc.php on line 40
INF

PHPにおける Warning メッセージはエラーとも例外とも異なります。Warning メッセージは出力されるものの、後続の処理は継続されるようになっています。

PHPプログラムの開発(例外の生成)

次に計算機クラス( SimpleCalc クラス)の除算を行う divide メソッドを修正して、 0 による除算が発生した場合は演算を行う前に例外( Exception インスタンス)をスローするように修正します。

<?php
class SimpleCalc
{
    // ...省略

    public function divide($x)
    {
        if ($x == 0) {
            $e = new Exception("Divide by 0.");
            throw $e;
        }
        $this->number = $this->number / $x;
    }

    // ...省略
}

divide メソッドでは引数 $x の値が 0 の場合 Exception インスタンスを生成して、 throw キーワードによって例外をスローしています。 throw キーワードは return キーワードのように、呼び出されるた時点でメソッドの処理を終了します。

計算機クラス( SimpleCalc.php )を修正したので、もう一度、実行プログラム( calc_runner.php )を動かしてみましょう。

$ php calc_runner.php
Fatal error: Uncaught Exception: Divide by 0. in /home/ec2-user/environment/SimpleCalc.php:39
Stack trace:
#0 /home/ec2-user/environment/calc_runner.php(9): SimpleCalc->divide(0)
#1 {main}
  thrown in /home/ec2-user/environment/SimpleCalc.php on line 39

実行結果を見ると先ほどのように Warning メッセージが出力されていないのがわかります。その代わりに Fatal error: Divide by 0. というメッセージを確認できます。これはPHPの Fatal error (致命的なエラー)が発生したことを意味しており、その原因として Exception がキャッチされなかった( Uncaught Exception )と説明が続きます。またメッセージの後には例外の発生箇所やスタックトレースの出力が確認できます。

ここでは例外をスローする方法として、 Exception クラスと throw キーワードについて取り上げました。以降はここでスローされた例外を適切に処理する方法を解説します。

参考:Exceptionクラス

Exception クラスはPHPに標準で用意されている例外クラスです。 Exception クラスの詳細については PHPマニュアル - Exceptionを参照してください。

まとめ

  • 例外( Exception )とはプログラムの予期せぬ事態を通知する仕組み
  • PHPにはユーザー例外の基底クラスとして Exception クラスが用意されている
  • Exception クラス(および、そのサブクラス)のインスタンスは throw キーワードによって、呼び出し元にスローできる