例外処理

例外処理ってなに?

例外処理」というのは、「例外」が投げられた場合の処理の事です。わけが解かりませんね。まず「例外」って何なのか?大雑把に言うと「エラー(問題)」の事です。通常は、エラーが発生したら「エラーですねちゃんちゃん」とそこで処理を中断するか何事も無かったかのようにスルーしてしまう所を、「例外処理」では、実行中にエラーが発生した場合の処理を記述しておいて、エラーが発生した場合でも処理の中断はせず、発生したエラーを「例外」として“投げ”、記述しておいた“エラー発生時の処理”を実行します。この“エラー発生時の処理”が「例外処理」であり、これを実現するのが「例外処理機能」です。この説明で理解できたら天才ですね(何)。

要するに、何か問題が発生した場合に“エラーですから!切腹!”するのではなく、“エラーみたいですよ”と報告し、その報告を受け取って何かしらの処置をするのが「例外処理」です。で、ここでいう“報告”が「例外」で、“報告する行為”を“「例外」を投げる”といいます。

tryブロックthrowcatchブロック

「例外処理」は、エラーが起こりそうな個所を「トライ」して、トライ中にエラーが発生したら「例外」を「スロウ(投げる)」してトライを中断し、投げられた「例外」を「キャッチ」して「エラー時用処理」を実行する、というものです(汗)。論よりコード。以下のサンプルと結果をご覧下さい。

<pre>
<?php
    echo "--- Exception Test ---\n\n";
    
    try{
        echo "****** Begin try ******\n\n";
        #引数が不正な関数コール(「Warning」警告)
        substr();
        echo "****** End try ******\n\n";
    }catch(Exception $e){
        echo "****** catch ******\n\n";
    }
    
    echo "--- Exception Test ---";
?>
</pre>
--- Exception Test ---

****** Begin try ******
Warning:「substr()」関数の引数が不正です。
****** End try ******

--- Exception Test ---

try」の後のブロック内の処理は実行され、「catch(Exception $e)」の後のブロック内の処理は実行されませんでした。

try」の後のブロック内では、意図的にエラーを発生させました。エラーが発生しましたが、特に何も起こりません。話が違うぞ(オイ)。

今回のサンプルは実は全く無意味なサンプルです。何が無意味かというと、エラーが起こっても「例外」を「スロウ(投げる)」していないので、「トライ」は全部実行され、「例外処理」も実行されていません。

「例外処理」は、「例外」が「スロウ(投げる)」されてはじめて実行されるのです。上のサンプルでは「例外」を「スロウ(投げる)」していないので、特に何も起こらなかったのです。なので次に、「例外」を「スロウ(投げる)」して「例外処理」を実行するサンプルを示します。以下のサンプルと結果をご覧下さい。

<pre>
<?php
    echo "--- Exception Test ---\n\n";
    
    try{
        echo "****** Begin try ******\n\n";
        echo "- An exception is thrown. -\n\n";
        throw new Exception("An exception is thrown.");
        echo "****** End try ******\n\n";
    }catch(Exception $e){
        echo "****** catch ******\n\n";
        echo '$e->getMessage() : ', $e->getMessage(), "\n\n";
        echo "\$e : \n", $e, "\n\n";
    }
    
    echo "--- Exception Test ---";
?>
</pre>
--- Exception Test ---

****** Begin try ******

- An exception is thrown. -

****** catch ******

$e->getMessage() : An exception is thrown.

$e : 
exception 'Exception' with message 'An exception is thrown.' in 発生場所
Stack trace:
#0 発生場所
#1 {main}

--- Exception Test ---

今回のサンプルには、「throw new Exception("An exception is thrown.");」なる一文が追加されました。

すると今回は「try」ブロックの処理はこの一文が実行された時点で中断され、「catch(Exception $e)」ブロックの処理が実行されました。

「トライ」中に「throw new Exception("An exception is thrown.")」によって「例外」が投げられ、その時点で「トライ」を中断し、投げられた「例外」を「catch(Exception $e)」によって「キャッチ」し、キャッチブロック内の「例外処理」が実行されたのです(汗)。

まず、「例外」を投げている所、「throw new Exception("An exception is thrown.");」という一文を見てみると、「new Exception("An exception is thrown.")」によって、「Exception」クラスのインスタンスを、コンストラクタに「"An exception is thrown."」という文字列を渡して生成しているのが解かります。で、「throw」によって生成したオブジェクトを“何処かに投げている”のがイメージできると思います。

投げられたオブジェクトの運命や如何に。

投げられたオブジェクトは、「catch(Exception $e)」によってキャッチされるんですが、よく見ると引数がオブジェクトの場合にクラス名を指定する関数定義のような格好をしています。実はそれと同じ様なもので「変数$e」は必ず「Exception」クラスのインスタンスが渡される事を想定した「catch」の引数なのです。「throw」によって投げられた「Exception」クラスのオブジェクトは「変数$e」に引数として代入され、キャッチブロック内で使用します。

catch(Exception $e)」の部分及びキャッチブロックは関数定義のように見え、そう捉える事も出来ますが、関数定義とは相違点があります。まず、引数のクラス名の指定を省略出来ません。あと、「変数$e」のスコープは関数の様にローカルスコープではなく、キャッチブロック外でも有効です。なので、「catch」の引数は同じスコープにある同名の変数と被らない様に注意する必要がありますが、「変数$e」を例外処理用の変数と決めて記述していけば問題ないでしょう。あと、「try」も「catch」も関数定義と同様に、後に続く処理が短文の場合であっても、処理部の「{}(波括弧)」を省略する事が出来ません。

で、ところで、「new Exception("An exception is thrown.")」によって、「Exception」クラスのインスタンスを生成しましたが、「Exception」クラスって何なのか?これが「例外」なんです。そう覚えるしかありません(オイ)。「Exception」クラスは、「Directory」クラスや「stdClass」クラスの様に、PHPで利用する為に、PHP側で予め定義されているクラスです。

この定義済みの「Exception」クラスがどのような形態をしているのか、下記のスクリプトを実行してみて見ましょう。

<pre>
<?php
    class MyException extends Exception{
      function __construct($message = NULL, $code = 0){
        parent::__construct($message, $code);
        $arr["class_vars"]    = get_class_vars("Exception");
        $arr["class_methods"] = get_class_methods("Exception");
        $arr["object_vars"]   = get_object_vars($this);
        print_r($arr);
        var_dump($this);
      }
    }
    
    new MyException("An exception is thrown.");
?>
</pre>

可視性の関係で、「Exception」クラスの派生クラス「MyException」クラスを作りました。この結果は以下のようになります。

Array
(
    [class_vars] => Array
        (
            [message] => 
            [string] => 
            [code] => 0
            [file] => 
            [line] => 
            [trace] => 
        )

    [class_methods] => Array
        (
            [0] => __construct
            [1] => getMessage
            [2] => getCode
            [3] => getFile
            [4] => getLine
            [5] => getTrace
            [6] => getTraceAsString
            [7] => __toString
        )

    [object_vars] => Array
        (
            [message] => An exception is thrown.
            [code] => 0
            [file] => ファイルパス
            [line] => 14
        )

)
object(MyException)#1 (6) {
  ["message:protected"]=>
  string(23) "An exception is thrown."
  ["string:private"]=>
  string(0) ""
  ["code:protected"]=>
  int(0)
  ["file:protected"]=>
  string(67) "ファイルパス"
  ["line:protected"]=>
  int(14)
  ["trace:private"]=>
  array(1) {
    [0]=>
    array(3) {
      ["file"]=>
      string(67) "ファイルパス"
      ["line"]=>
      int(14)
      ["function"]=>
      string(7) "unknown"
    }
  }
}

4つの「プロテクテッド」なプロパティと2つの「プライベート」なプロパティ、計6つのプロパティと、8つのメソッドを持っているのが解かります。

詳細については置いといて、前のサンプルで「$e->getMessage()」をコールしたら、インスタンスの生成時に渡した文字列を得る事が出来ました。また、「変数$e」を文字列として出力した所、インスタンス生成時に渡した文字列を含めた様々な情報を得る事が出来ました。こんな感じで、「スロウ」されて「キャッチ」した「例外オブジェクト」を使いながら、「例外処理」を行います。

と、ここまでで全く実用性を感じさせない「例外処理機能」ですが、最初のサンプルで意図的に発生させたエラーも意に介さず、手動で「例外」を投げないと「例外処理」が実行されない所を見る限り、「例外処理機能」というものに対して疑念を持ち始めている事かと思います。JavaScriptでは(「Java」ではありません。自分は「Java」を使った事がありませんで、あくまで「JavaScript」です)、「トライ」ブロック内でエラーが発生すると、自動的に各種の「例外」を投げてくれたんですが、PHPには(今の所?)そのような機能は無いようです(一部の新しいビルトイン関数では「例外」を投げるものもある)。なのでPHPでは、そのような機能を自分で実装しなくてはなりません。

具体的には、まず何かをするスクリプトを作成し、そのスクリプト内で起こりうる問題を「例外」として、各種問題用「例外クラス」を定義し、スクリプト内の、問題の発生が予想される場所に「例外処理」を記述し、問題があれば「トライ」ブロック内で発生した問題用に用意しておいた「例外オブジェクト」を投げます。結局手動です。

問題発生時に「例外」を投げさせる

前のサンプルでは、「トライ」ブロック内で直接「例外」を投げていましたが、これではただ単に投げるだけで、全く無意味です。問題があったときに投げるようにしないと意味がありません。という事で、“問題があった時に「例外」を投げる”サンプルと結果を以下に。

<pre>
<?php
    function division($num1, $num2){
        try{
            if($num2 === 0)
                throw new Exception('「0」で割る事は出来ません。');
            else if(!is_numeric($num1) || !is_numeric($num2))
                throw new Exception('値が不正です。');
            return $num1 / $num2;
        }catch(Exception $e){
            return $e->getMessage();
        }
    }
    
    echo division(10, 2), "\n";
    echo division(10, ""), "\n";
    echo division(0, 10), "\n";
    echo division(10, 0), "\n";
?>
</pre>

文字化けを考慮してここまで日本語を使わないで来ましたが、これにも限界を感じたので(オイ)日本語を使ってしまいました。視認性の問題もあり、今更という気もしますが、これからは日本語を使うようにします。以降のサンプルを実行する際は文字化けしないように各自修正するなりして下さい。

5
値が不正です。
0
「0」で割る事は出来ません。

「例外処理機能」を使うまでもない事が「例外処理」で実現できました。というより「例外処理」を使わない方がシンプルに書けそうです。

そんな事はいいとして、「throw」で投げる事が出来るのは、「Exception」クラスのインスタンスだけではありません。「Exception」クラスの派生クラスのインスタンスであれば、「例外」として投げる事が出来ます。この場合は、「catch」の引数に指定するクラス名を、投げられる「例外オブジェクト」のクラス名にする必要があります。逆に、「Exception」クラスと無関係なクラスのインスタンスを投げたり、また投げられた「例外オブジェクト」のクラス名が指定されないと、「E_ERROR」エラーとなります。また、「try」の後の「catch」は複数設置する事が出来ます。この場合、最初の「catch」から順に、投げられた「例外オブジェクト」のクラス名と引数に指定したクラス名がマッチするかチェックしていき、最初にマッチした所の「キャッチ」ブロックの処理が実行されます。

以下のサンプルと結果をご覧下さい。

<pre>
<?php
    class MyException1 extends Exception{}
    class MyException2 extends Exception{}
    class MyException3 extends MyException1{}
    class MyException4 extends MyException2{}
    class NotException{}
    
    for($i = 0; $i < 6; $i++){
        try{
            switch($i){
                case 0:
                    echo "- Exception is thrown. -\n";
                    throw new Exception;
                case 1:
                    echo "- MyException1 is thrown. -\n";
                    throw new MyException1;
                case 2:
                    echo "- MyException2 is thrown. -\n";
                    throw new MyException2;
                case 3:
                    echo "- MyException3 is thrown. -\n";
                    throw new MyException3;
                case 4:
                    echo "- MyException4 is thrown. -\n";
                    throw new MyException4;
                case 5:
                    echo "- NotException is thrown. -\n";
                    throw new NotException;
            }
        }catch(MyException1 $e){
            echo "Catching MyException1.\n";
            echo get_class($e), " was caught.\n\n";
        }catch(Exception $e){
            echo "Caching Exception.\n";
            echo get_class($e), " was caught.\n\n";
        }catch(MyException2 $e){
            echo "Caching MyException2.\n";
            echo get_class($e), " was caught.\n\n";
        }catch(NotException $e){
            echo "Caching NotException.\n";
            echo get_class($e), " was caught.\n\n";
        }
    }
?>
</pre>
- Exception is thrown. -
Caching Exception.
Exception was caught.

- MyException1 is thrown. -
Catching MyException1.
MyException1 was caught.

- MyException2 is thrown. -
Caching Exception.
MyException2 was caught.

- MyException3 is thrown. -
Catching MyException1.
MyException3 was caught.

- MyException4 is thrown. -
Caching Exception.
MyException4 was caught.

- NotException is thrown. -
Fatal error:「例外オブジェクト」が「Exception」クラスと無関係です。

「Exception」クラスの派生クラス「MyException1」クラスと「MyException2」クラス、「MyException1」クラスの派生クラス「MyException3」クラス、「MyException2」クラスの派生クラス「MyException4」クラスと、「Exception」クラスと無関係なクラス「NotException」クラスを定義して、それぞれを「例外」として投げました。

投げられた「例外オブジェクト」がそれぞれ何処でキャッチされたか見てみると、「Exception」クラスの「例外オブジェクト」は「Exception」クラスを指定する「catch(Exception $e)」に、「MyException1」クラスのインスタンスは「catch(MyException1 $e)」に、「MyException2」クラスのインスタンスは「catch(Exception $e)」に、「MyException3」クラスのインスタンスは「catch(MyException1 $e)」に、「MyException4」クラスのインスタンスは「catch(Exception $e)」に、「NotException」クラスのインスタンスは「Exception」クラスと無関係なので「例外」とみなされずに「E_ERROR」エラーを発しました。

この様な結果になった理由は、関数定義時の、オブジェクト引数のクラス名指定の場合と同じ考え方です。例えば、「MyException4」クラスは「MyException1」クラスの派生クラスであり、更に「MyException1」クラスは「Exception」クラスの派生クラスなので、「MyException4」クラスのインスタンスは「catch(Exception $e)」にキャッチされたのです。

これを踏まえて、割算をするサンプルを書き直してみます。

<pre>
<?php
    class DivisionException extends Exception{}
    class DivisionIsZeroException extends DivisionException{
        protected $message = '「0」で割る事は出来ません。';
    }
    class DivisionWrongValueException extends DivisionException{
        protected $message = '値が不正です。';
    }
    
    class Division{
        private $result;
        public function __construct($num1, $num2){
            if($num2 === 0)
                throw new DivisionIsZeroException;
            else if(!is_numeric($num1) || !is_numeric($num2))
                throw new DivisionWrongValueException;
            $this->result = $num1 / $num2;
        }
        public function getResult(){
            return $this->result;
        }
    }
    
    function division($num1, $num2){
        try{
            $division = new Division($num1, $num2);
            return $division->getResult();
        }catch(DivisionException $e){
            return $e->getMessage();
        }
    }
    
    echo division(10, 2), "\n";
    echo division(10, ""), "\n";
    echo division(0, 10), "\n";
    echo division(10, 0), "\n";
?>
</pre>
5
値が不正です。
0
「0」で割る事は出来ません。

function division($num1, $num2){}」がシンプルになりましたが、その代わりクラスが4つも定義されました。

まず、「Exception」クラスを継承した「DivisionException」クラスを定義しました。これは“割算における「例外」”です。次に「DivisionException」クラスを継承した「DivisionIsZeroException」クラスと「DivisionWrongValueException」クラスを定義しました。それぞれ、“分母が「0」であるという「例外」”と“値が不正であるという「例外」”で、問題発生時に投げられます。

“割算”は数値演算なので、数値以外の値で演算する事は出来ません。また数学的に「0」で割る事は出来ません。数値以外で演算しようとしたり、「0」で割ろうとする事はどちらも割算的に想定外なことで、要するに割算的に「例外」なのです。

また各クラスで「プロパティ$message」に初期値を設定しています。この「プロパティ$message」は、「例外オブジェクト」の生成時にコンストラクタへ渡された場合に、渡された文字列がセットされる所です。「Exception」クラスでの初期値は空文字列ですが、「DivisionIsZeroException」クラスと「DivisionWrongValueException」クラスはそれぞれ固有の「例外」なので、「例外オブジェクト」を生成する再にわざわざメッセージを渡すよりも最初から決定しておいた方が楽だし自然です。

「Division」クラスは、割算を行う為のクラスです。このクラスでは、コンストラクタに渡された2つの値をチェックし、問題があればそれ用の「例外」を投げ、問題がなければ(「例外」が投げられていなければ)演算結果をプロパティに保存します。

最終的に「division()」関数は、問題がなければ「$division->getResult()」の返り値を、問題があって「例外」が投げられたら(何れの「例外」も「catch(DivisionException $e)」にキャッチされて)「$e->getMessage()」の返り値を返すというものになりました。

不正な値で割算をしようとした時に自動的に「例外」が投げられればこんなに面倒にはならない、というより割算の値のチェック程度の事に「例外処理」を使う必要は無かったですね。

ハァ?

そんなこんなで(どんなこんなで)、「例外処理」を使うかどうかはケースバイケースです。使った方が良さそうな時に使いましょう。

「例外処理」のネストその他

「例外処理」のネスト

「例外処理」はネストする事が出来ます。ネストすると、投げた「例外」をキャッチ出来る「キャッチャー?」が同レベルに無い場合は、上位レベルの「キャッチャー?」を探します。また、「キャッチ」ブロック内からも「例外」を投げられるようになり、まず上位レベルの「キャッチャー?」を探します。

以下のサンプルと結果をご覧下さい。

<pre>
<?php
    class MyException1 extends Exception{}
    class MyException2 extends Exception{}
    
    try{
        try{
            try{
                echo "- MyException1 is thrown. -\n";
                throw new MyException1;
            }catch(MyException2 $e){}
        }catch(Exception $e){
            echo "Caching Exception.\n";
            echo get_class($e), " was caught.\n\n";
            echo "- MyException2 is thrown. -\n";
            throw new MyException2;
        }
    }catch(Exception $e){
        echo "Caching Exception.\n";
        echo get_class($e), " was caught.\n\n";
        echo "- MyException2 is thrown. -\n";
        throw new MyException2;
    }
?>
</pre>
- MyException1 is thrown. -
Caching Exception.
MyException1 was caught.

- MyException2 is thrown. -
Caching Exception.
MyException2 was caught.

- MyException2 is thrown. -

Fatal error:「例外:MyException2」がキャッチされませんでした。
Stack trace:
#0 ファイルパス(22): unknown()
#1 {main}
  thrown in ファイルパス on line 22

throw new MyException1;」で「例外」を投げましたが、同レベルではキャッチされなかったので、上位レベルを探し、「catch(Exception $e)」でキャッチされました。今度はその「キャッチ」ブロック内で、「throw new MyException2;」で「例外」を投げ、上位レベルを探し、「catch(Exception $e)」でキャッチされました。更にその「キャッチ」ブロック内で、「throw new MyException2;」で「例外」を投げますが、既に最上位レベルなのでキャッチされずに、「E_ERROR」エラーとなります。

「例外スロウ」のキャンセル

「例外オブジェクト」の生成時に、コンストラクタ内で別の「例外オブジェクト」を生成して投げるとどうなるでしょうか。以下のサンプルと結果をご覧下さい。

<pre>
<?php
    class NotException{}
    class MyException extends Exception{
        public function __construct($message = NULL, $code = 0){
            parent::__construct($message, $code);
            throw new NotException;
        }
    }
    
    try{
        throw new MyException;
    }catch(Exception $e){}
?>
</pre>
Fatal error:「例外オブジェクト」が「Exception」クラスと無関係です。

まず「throw new MyException;」で「例外オブジェクト」を生成して投げようとしています。「MyException」クラスのコンストラクタ内では、「throw new NotException;」で「偽例外オブジェクト」を生成して投げようとしています。結果は、「偽例外オブジェクト」を投げないで下さいと「E_ERROR」エラーになりました。要するに、最初の「スロウ」はキャンセルされて、その後の「スロウ」が実行された格好になりますが、実際は、最初の「throw」が実行される前に、コンストラクタ内の「throw」が実行されて投げられたってだけの事です。

「例外オブジェクト」のメソッド

デフォルトの(「Exception」クラスの)「例外オブジェクト」は、コンストラクタ等を含めて8つのメソッドを備えています。これらのメソッドがどういうものなのか見てみます。以下のサンプルと結果をご覧下さい。

<pre>
<?php
    class MyException extends Exception{
        public function __construct($mes, $code){
            throw new Exception($mes, $code);
        }
    }
    
    class HODE{
        public function __construct($mes){
            $this->hode_method($mes);
        }
        private function hode_method($mes){
            try{
                throw new MyException($mes, 12345);
            }catch(Exception $e){
                echo '&gt;$e->getMessage();', "\n", $e->getMessage(),"\n\n";
                echo '&gt;$e->getCode();',    "\n", $e->getCode(),   "\n\n";
                echo '&gt;$e->getFile();',    "\n", $e->getFile(),   "\n\n";
                echo '&gt;$e->getLine();',    "\n", $e->getLine(),   "\n\n";
                echo '&gt;$e->getTrace();',   "\n";
                print_r( $e->getTrace() );
                echo "\n", '&gt;$e->getTraceAsString();', "\n",
                                               $e->getTraceAsString();
                echo "\n\n", '&gt;__toString()', "\n", $e;
            }
        }
    }
    
    function hode_func(){
        new HODE("An exception is thrown.");
    }
    
    hode_func();
?>
</pre>
>$e->getMessage();
An exception is thrown.

>$e->getCode();
12345

>$e->getFile();
ファイルパス

>$e->getLine();
5

>$e->getTrace();
Array
(
    [0] => Array
        (
            [file] => ファイルパス
            [line] => 5
            [function] => __construct
            [class] => MyException
            [type] => ::
        )

    [1] => Array
        (
            [file] => ファイルパス
            [line] => 15
            [function] => __construct
            [class] => MyException
            [type] => ->
            [args] => Array
                (
                    [0] => An exception is thrown.
                    [1] => 12345
                )

        )

    [2] => Array
        (
            [file] => ファイルパス
            [line] => 11
            [function] => hode_method
            [class] => HODE
            [type] => ->
            [args] => Array
                (
                    [0] => An exception is thrown.
                )

        )

    [3] => Array
        (
            [file] => ファイルパス
            [line] => 31
            [function] => __construct
            [class] => HODE
            [type] => ->
            [args] => Array
                (
                    [0] => An exception is thrown.
                )

        )

    [4] => Array
        (
            [file] => ファイルパス
            [line] => 34
            [function] => hode_func
            [args] => Array
                (
                )

        )

)

>$e->getTraceAsString();
#0 ファイルパス(5): MyException::__construct()
#1 ファイルパス(15): MyException->__construct('An exception is...', 12345)
#2 ファイルパス(11): HODE->hode_method('An exception is...')
#3 ファイルパス(31): HODE->__construct('An exception is...')
#4 ファイルパス(34): hode_func()
#5 {main}

>toString()
exception 'Exception' with message 'An exception is thrown.' in ファイルパス:5
Stack trace:
#0 ファイルパス(5): MyException::__construct()
#1 ファイルパス(15): MyException->__construct('An exception is...', 12345)
#2 ファイルパス(11): HODE->hode_method('An exception is...')
#3 ファイルパス(31): HODE->__construct('An exception is...')
#4 ファイルパス(34): hode_func()
#5 {main}

メソッドの機能をまとめると、下表の様になります。

「例外オブジェクト」が持つメソッドの返り値
メソッド返り値
__construct()」メソッド 無し(コンストラクタ)
getMessage()」メソッド 「プロパティ$message」の値
getCode()」メソッド 「プロパティ$code」の値
getFile()」メソッド 「プロパティ$file」の値
getLine()」メソッド 「プロパティ$line」の値
getTrace()」メソッド 「プロパティ$trace」の値(配列)
getTraceAsString()」メソッド 「プロパティ$trace」の値を文字列にして返す
__toString()」メソッド 「例外オブジェクト」を文字列として評価した時に、
getTraceAsString()」メソッドと似た値を返す

コンストラクタは2つの引数を持っていて、第一引数には「メッセージ」を、第二引数には「コード」を指定でき、それぞれ省略できます。ちなみに、間違った指定をすると以下の様なアラートと共に「E_ERROR」エラーを発します。

「Wrong parameter count for exception([string $exception[, long $code]])」というアラートを出す。

また、「例外オブジェクト」が持つ各プロパティは下表の通りです。

「例外オブジェクト」が持つプロパティ
プロパティ格納される値
protected $message 「例外オブジェクト」生成時に渡されたメッセージ(文字列)
protected $code 「例外オブジェクト」生成時に渡された識別コード(整数)
protected $file 「例外」が投げられたファイルへの絶対パス
protected $line 「例外」が投げられた位置(行番号)
private $trace 「例外」が投げられるまでの足跡(昔に逆戻る)を配列で格納
private $string 不明(「Exception」クラス内部で使用するので意識する必要なし)

「Exception」クラスは上表の様なプロパティとメソッドを備えています。「Exception」クラスの派生クラスを作る場合は、メソッドやプロパティの追加、コンストラクタ等特殊メソッドのオーバーライドが出来ますが、コンストラクタ等特殊メソッド以外のメソッドは訳あって(「final修飾子」で宣言されているため)オーバーライドする事が出来ません。という事は、「プライベート」なプロパティの再定義は意味をなしません。派生クラスでは「プロテクテッド」なプロパティを「プロテクテッド」なプロパティとして再定義出来ます。

例外ハンドラ

投げられた「例外オブジェクト」が最終的に何処にもキャッチされないと、通常は「E_ERROR」エラーを発しますが、「例外ハンドラ」というものをセットしておくと、キャッチされなかった「例外オブジェクト」を引数に、この「例外ハンドラ」がコールされます。

「例外ハンドラ」を有効にするには、まず“「例外ハンドラ」用のユーザ定義関数”を定義し、この関数名を引数に「set_exception_handler()」関数をコールします。これによって指定された名前の関数は「例外ハンドラ」として機能するようになります。

「例外ハンドラ」は何度でも設定でき、また「restore_exception_handler()」関数によって1つ前の状態に復旧できます。

「例外ハンドラ」がコールされると、その時点で処理は終了してしまいます。

set_exception_handler()関数
string set_exception_handler(callback 例外ハンドラ)
投げられた例外がキャッチされなかった場合にコールされる“例外ハンドラ”を設定します。
「例外ハンドラ」には、文字列で例外ハンドラ名を指定するか、第一要素が例外ハンドラをメソッドに持つオブジェクトもしくはクラス名で、第二要素がその例外ハンドラメソッド名である配列を指定します。
指定された「例外ハンドラ」をセットし、セット直前の例外ハンドラ名を返します。未設定の場合は空文字列を返し、エラー時には「FALSE」を返します。
restore_exception_handler()関数
void restore_exception_handler(void)
例外ハンドラをひとつ前の設定に復旧します。

以下のサンプルと結果をご覧下さい。

<pre>
<?php
    #「例外ハンドラ」用の関数を定義する
    function my_exception_handler(Exception $e){
        echo "*** my_exception_handler() function ***\n\n";
        echo $e->getMessage(), "\n\n";
    }
    
    #「例外ハンドラ」用のメソッドを持つクラスを定義する
    class MyCLASS{
        private $hode = "HODENASU";
        public function my_exception_handler1(Exception $e){
            echo "*** MyCLASS::my_exception_handler1() method ***\n\n";
            echo $e->getMessage(), "\n\n";
        }
        public function my_exception_handler2(Exception $e){
            echo "*** MyCLASS::my_exception_handler2() method ***\n\n";
            echo $e->getMessage(), "\n\n";
        }
    }
    
    #新しく「例外ハンドラ」名をセットして、直前の名前を取得
    
    #「例外ハンドラ」に関数名を指定する
    $arr[] = set_exception_handler("my_exception_handler");
    
    #「例外ハンドラ」にクラス名とそれが持つメソッド名を指定する
    $arr[] = set_exception_handler(
                     array("MyCLASS", "my_exception_handler1")
                                 );
    
    #「例外ハンドラ」にオブジェクトとそれが持つメソッド名を指定する
    $arr[] = set_exception_handler(
                     array(new MyCLASS, "my_exception_handler2")
                                 );
    
    $arr[] = set_exception_handler($arr[1]); //「"my_exception_handler"」
    
    print_r($arr);
    
    #「例外ハンドラ」をひとつ前の設定に復旧する(2回)
    restore_exception_handler();
    restore_exception_handler();
    
    echo "\n--- Exception Test ---\n\n";
    
    #「キャッチャー?」が不在な所から「例外」を投げる。
    #「例外ハンドラ」が有効だと、こんな所からも「例外」を投げられる。
    throw new Exception("Throwing out of catch block.");
    
    echo "--- Exception Test ---";
?>
</pre>
Array
(
    [0] => 
    [1] => my_exception_handler
    [2] => Array
        (
            [0] => MyCLASS
            [1] => my_exception_handler1
        )

    [3] => Array
        (
            [0] => MyCLASS Object
                (
                    [hode:private] => HODENASU
                )

            [1] => my_exception_handler2
        )

)

--- Exception Test ---

*** MyCLASS::my_exception_handler1() method ***

Throwing out of catch block.

「例外ハンドラ」用に、関数と、メソッドを含むクラスを定義しました。その後、「set_exception_handler()」関数で4回セットし、「restore_exception_handler()」関数で2つ前の設定に戻しました。

「トライ」ブロック外で「例外オブジェクト」を投げ、これでは何処にもキャッチされないので、「例外ハンドラ」が「キャッチャー」の代わりにコールされます。で、2つ前の設定はクラスのメソッドバージョンなので、こちらが「例外ハンドラ」としてコールされ、そこで処理は終了しました。

定義済みの「例外」

PHPで「例外処理」を使うときは、投げる「例外オブジェクト」のクラスと投げる機構を自分で用意しなくてはなりませんでしたが、一部の新めのビルトイン関数の中には、エラー時に「例外オブジェクト」を投げてくれるものもあります。投げられる「例外オブジェクト」のクラスは、その関数用の、定義済みクラスです。

“定義済みの「例外」”にはどのようなものがあるのか、下記のスクリプトを実行して見てみます。

<pre>
<?php
    #定義済みクラスを調べる
    foreach(get_declared_classes() as $class_name){
        #「Exception」クラスを継承しているクラスなら「例外」
        if(is_subclass_of($class_name, "Exception"))
            echo $class_name, "\n";
    }
?>
</pre>
ReflectionException
com_exception
SQLiteException
DOMException

定義済みのクラス名の中から、「Exception」クラスを継承しているクラス名を抽出しました。今の所(PHP5.0.3)この4つが“定義済みの「例外」”として定義されています。

この中の「DOMException」クラスは「DOM関数(旧DOM XML関数)」用に定義された「例外」で、エラー時にこの「例外オブジェクト」を投げます。

以下のサンプルと結果をご覧下さい。

<pre>
<?php
    $doc = new DOMDocument;
    
    $html = $doc->createElement("html");
    
    $doc->appendChild($html);
    $doc->removeChild($html);
    
    try{
        $doc->removeChild($html);
    }catch(DOMException $e){
        echo $e->getMessage(), "\n\n";
        echo $e;
    }
    
    echo "\n\nHODENASU!!!";
    
    @$doc->removeChild($html);
    
    echo "\n\nHODEGASU!!!";
?>
</pre>
Not Found Error

exception 'DOMException' with message 'Not Found Error' in ファイルパス:11
Stack trace:
#0 ファイルパス(11): DOMDocument->removeChild(Object(DOMElement))
#1 {main}

HODENASU!!!
(投げた「例外」がキャッチされずにスクリプト終了)

簡単に説明すると、ドキュメントに子ノードを追加し、その後追加した子ノードを削除しようとしている所ですが、「トライ」ブロック内で削除済みの子ノードを削除しようとしているため、「Not Found Error」だという「例外」が投げられました。

当然、投げられた「例外オブジェクト」がキャッチされなければ、単に「E_ERROR」エラーとなってスクリプトは終了します。

作成日:2004年12月31日 最終更新日:2004年12月31日
【印刷モード風モード で表示】