例外処理

例外とは

例外とは、正規の処理フローからは外れ、そのまま処理続行が出来ないような状況のことを言います。

例えば0除算を行っただとか、テキストファイルを読み込むのにそのテキストファイルが存在しないだとか、データベース処理においてSQL文が構文エラーだったとか。

普通、そういった事が発生する可能性を想定して処理の直前にチェックを入れ、問題のある場合の処理分岐を行うって事が考えられます。
割り算の前に割る数が0ではないかをチェックし、0ならエラーメッセージを表示して処理中断とか、テキストファイルの中身を読み込む処理の場合、テキストファイルが存在するか確認して、なければ作成、みたいな感じです。

PHPにはこういう例外発生時の処理を行う仕組みが存在します。

例外処理の構文

まずは構文です。

<?php

try {
    throw new Exception('エラーが発生しました');
} catch (Exception $e) {

}

?>

tryブロック内で何か例外が発生した場合、catchブロック内へ処理が飛び、catchブロック内の処理が終了後、以降の処理へ進みます。
tryブロック内で何も起こらなかった場合、tryブロック内の処理が全て終了後、catchブロックは無視され、以降の処理へ進みます。

で、ここで「例外が発生する」と表現しているのは、「例外クラスをthrowする」ことを指しています。
throw new Exception(・・・)とすることで、その時点でtryブロック内の処理が中断し、catchへ飛びます。

なので、基本的には明示的にthrowしない限りはtry~catchの意味はありません

例外処理の活用

実際の使い方を見てみます。
例えば0除算の場合を例に見てみましょう。

<?php

class Calc
{
    public static function division($int1, $int2)
    {
        try {
            if ($int2 == 0) {
                throw new Exception('0で割ろうとしました');
            }
            $answer = $int1 / $int2;
            echo $answer;
            echo '割り算が終了しました';
        } catch (Exception $e) {
            echo $e->getMessage();
            echo '割り算が異常終了しました';
        }
    }
}

Calc::division(10, 2);
Calc::division(10, 0);

?>
5
割り算が終了しました
0で割ろうとしました
割り算が異常終了しました

でもこれって、ifの中で、catchでやっているのと同じ事をやってreturnすれば同じことです。

Javaとか.NETなんかのオブジェクト指向言語では、なんか問題があったら必ず何かの例外を勝手に投げてくれます。
でもPHPは勝手に例外を投げてくれません、例外は自分で投げるしかありません。

なのでPHPではあんまり意味がないように見えます。
いや実際あんまり意味ないです。

但し、最近のバージョンで追加されたような関数やクラスなんかは例外を投げてくれたりするし、そういうのんには使えます。

ただメリットとしては、エラー時の処理をcatchブロックにまとめられるって事が言えます。
tryブロック内では何かあったときにはとりあえずthrowってやっとけばいいだけで、そのときにどういう処理を行うかはcatchブロックにまとめることで全体に見やすくはなります。

また、PHPの標準関数を直接使わないってのも手です。
標準関数が投げてくれないなら自分で投げるようにするんです。
つまり、標準関数をラップしたようなクラスを作って、エラーになりそうな可能性を全て想定して例外を投げるんです。

<?php

class File
{
    public static function read($path)
    {
        if (false == file_exists($path)) {
            throw new Exception('ファイルが存在しません');
        }
        $fp = @fopen($path, 'r');
        if (false === $fp) {
            throw new Exception('ファイルのオープンに失敗しました');
        }
        $data = @stream_get_contents($fp);
        if (false === $fp) {
            throw new Exception('ファイルの読込に失敗しました');
        }
        return $data;
    }
}

class StringViewer
{
    public function display($path)
    {
        try {
            $data = File::read($path);
            echo $data;
        } catch (Exception $e) {
            die($e->getMessage());
        }
    }
}

$viewer = new StringViewer();
$viewer->display('C:/aaa.txt');

?>
ファイルが存在しません

なんの事かよく分からないかも知れませんが、
上の例で言うと、
本来、fopen関数を使ってファイルをオープンするところを、
Fileクラスのreadメソッドを代わりに使用しています。
なので、Fileクラスは標準関数クラスというような位置づけであらかじめ作っておくのです。

throwされたExceptionは、その関数内でcatchされなかった場合、更にその呼び出し元へthrowされます。そうやってcatchされるまで呼び出し元をたどっていき、最後までcatchされなかった場合、エラーが発生します。

<?php

class StringViewer
{
    public function display($path)
    {
        File::read($path);
    }
}

$viewer = new StringViewer();
$viewer->display('C:/aaa.txt');

?>
Fatal error: Uncaught exception 'Exception' with message 'ファイルが存在しません' in ・・・

こうなるので、throwに対しては必ずどこかでcatchする必要があります。

前述のように、catchされるまで呼び出し元をたどっていくので、一番基点部分をtry~catchすればいい事になります。

<?php

class StringViewer
{
    public function display($path)
    {
        File::read($path);
    }
}

try {
    $viewer = new StringViewer();
    $viewer->display('C:/aaa.txt');        
} catch (Exception $e) {
    die($e->getMessage());
}

?>
ファイルが存在しません

このように、処理基点となるスクリプトをtry~catchすることで、全ての例外がそこで捕まえることが出来ます。

実際のWebシステムでは、エラーの詳細が訪問者に見えるのはあまり好ましくないので、全ての例外は基点でcatchし、catchブロックでは訪問者に対しては固定のエラーメッセージ、例えば「システムエラーが発生しました」などを表示しておき、エラーの詳細はログファイルに出力しておく、というのがいいのではないでしょうか。

<?php

try {

    ・
    ・
    ・
   
} catch (Exception $e) {
    echo 'システムエラーが発生しました';
    Logger::write($e->getMessage());
}


?>