PHP5の例外機能の使い方

  • PHP5ってどうするんだ
    • PHP5になると美味しいことたくさん
    • 色々組み込まれてる。XMLもDBも美味しい。
    • 例外処理よくわからないから誰か教えて
やめだやめだ!Ethnaでいくぞ! - 肉とビールとパンケーキ by @sotarok

PHP5の例外機能をどう使えばいいかよく分からないという声をちらほら聞くので、チュートリアルぽく例外の説明をしたいと思います。

従来のエラー処理

まずは従来の一般的なエラー処理のやり方から。
例外機能のない従来のPHP4プログラミングでは、ある関数の中でエラーを返したい場合は、そのエラーを表す何がしかの値を関数の戻り値として返すことになります。

そのエラーを表す値を毎回決めるのは面倒なので、PEARのエラー処理スキームを利用することが広く行われています。その場合エラーが起きたらPEAR_Errorオブジェクトが返されることになります。

<?php
require_once 'PEAR.php';

function func() {
    // もしエラーだったら
    return PEAR::raiseError('Error occurred');
}

$result = func();
if (PEAR::isError($result)) {
    die($result->getMessage());
}
?>

上記を見て分かるように、エラーは関数の戻り値でのみ示すことになりますので、ちゃんとしたプログラムの場合は関数呼出しの度にエラーチェックをすることになります。また関数呼び出しのボトムで起こったエラーをトップまで伝えるには、生成したエラーオブジェクトを順繰りにreturnする必要があります。

<?php
require_once 'PEAR.php';

function func_sub1() {
    return PEAR::raiseError('func_sub1 error occurred');
}

function func_sub2() {
    return PEAR::raiseError('func_sub2 error occurred');
}

function func_sub3() {
    return PEAR::raiseError('func_sub3 error occurred');
}

function func() {
    $result = func_sub1();
    if (PEAR::isError($result)) {
        return $result;
    }
    $result = func_sub2();
    if (PEAR::isError($result)) {
        return $result;
    }
    $result = func_sub3();
    if (PEAR::isError($result)) {
        return $result;
    }
}

$result = func();
if (PEAR::isError($result)) {
    die($result->getMessage());
}
?>

関数func()では、関数を3つ呼ぶだけなのに毎回エラーチェックが必要になり、面倒な上に、なにより見にくいコードになってしまいます。

PHP5の例外を使ってみる

そこでエラーを例外を使って処理してみましょう。
まずは例外の基本的な使い方から。

<?php
function func() {
    // もしエラーが起きたら例外をthrow
    throw new Exception('Error occurred');
}

try {
    func();
} catch (Exception $e) {
    die($e->getMessage());
}
?>

PHPの例外機能はC++Javaなどと同様に、try ブロックで発生した例外をcatchで受け取るという文法になっています。

throwできるものはオブジェクトに限られ、PHPではExceptionクラスかそのサブクラスのオブジェクトをthrowする慣習になっています。

では先ほどの例を例外を使って書き直してみます。

<?php
function func_sub1() {
    throw new Exception('func_sub1 error occurred');
}

function func_sub2() {
    throw new Exception('func_sub2 error occurred');
}

function func_sub3() {
    throw new Exception('func_sub3 error occurred');
}

function func() {
    func_sub1();
    func_sub2();
    func_sub3();
}

try {
    func();
} catch (Exception $e) {
    die($e->getMessage());
}
?>

この例で分かるように、例外なしでは毎回エラーチェックをしていた関数func()がシンプルに関数を呼ぶだけになりました。func_sub1()で発生した例外はfunc()を飛び越えて、最も発生元の呼び出しレベルに近い(この場合はトップレベル)catchブロックに直接届きます。

特定の例外のみ処理を変更する

tryブロックは入れ子にすることもできます。特定のエラー条件の場合だけ処理を変更したい場合は、その場所にtryブロックを作って対応したcatch内で処理を切り替えればOKです。

以下の例では関数func()にtryブロックを作り、func_sub1()で発生したエラーのみ無視しています。

<?php
define('ERR_SUB1', 1);
define('ERR_SUB2', 2);
define('ERR_SUB3', 3);

function func_sub1() {
    throw new Exception('func_sub1 error occurred', ERR_SUB1);
}

function func_sub2() {
    throw new Exception('func_sub2 error occurred', ERR_SUB2);
}

function func_sub3() {
    throw new Exception('func_sub3 error occurred', ERR_SUB3);
}

function func() {
    try {
        func_sub1();
        func_sub2();
        func_sub3();
    } catch (Exception $e) {
        if ($e->getCode() == ERR_SUB1) {
           // func_sub1()の例外だけ無視
        } else {
           // その他の場合は例外を投げ直す
           throw $e;
        }
    }
}

try {
    func();
} catch (Exception $e) {
    die($e->getMessage());
}
?>

他にもcatchブロックを受け取るクラスごとに複数並べたりできますが、よく使われる形は上記のいずれかのパターンだと思います。