例外を投げるモジュールのCソース

PHP5から例外が使えますが、「例外を投げるモジュール」のCソースはどうなってるのか、ちょっと調べてみました。
結論を先に言うと、以下の2つを書くことになるようです。
- 例外クラス用ポインタの定義(zend_class_entry型のポインタ)
- PHP_MINIT_FUNCTION(モジュール単位の初期化関数)内で例外クラスの初期化(INIT_CLASS_ENTRYマクロ)と登録(zend_register_internal_class/zend_register_internal_class_ex)
PEAR::CodeGen_PECLのspecファイルで書くと以下のような感じです。ExceptionTestクラスのthrowExceptionメソッドを呼び出すと、例外ExceptionTestExceptionを投げてきます。
<?xml version="1.0"?> <extension name="exception_test" version="0.0.1"> <summary>from exception_test</summary> <description>from exception_test</description> <license>PHP</license> <class name="ExceptionTest"> <function name="throwException"> <proto>boolean throwException()</proto> <code> <![CDATA[ zend_throw_exception_ex(exception_test_exception_ce, 0 TSRMLS_CC, "Exception threw"); RETURN_TRUE; ]]> </code> <test> <code> <![CDATA[ try { $obj = new ExceptionTest(); $obj->throwException(); echo 'NG'; } catch (Exception $e) { echo 'OK'; }]]> </code> <result>OK</result> </test> </function> </class> <function role="internal" name="MINIT"> <code> <![CDATA[ zend_class_entry ce; INIT_CLASS_ENTRY(ce, "ExceptionTestException", NULL); exception_test_exception_ce = zend_register_internal_class_ex(&ce, zend_exception_get_default(TSRMLS_C), NULL TSRMLS_CC); ]]> </code> </function> </extension>
このExceptionTestExceptionクラス、PHP組み込みのExceptionクラスのサブクラスになっています。確認のためにZend/zend_exceptions.c(PHP5.2.1)を見てみると、以下のようになっていました。
zend_class_entry *default_exception_ce;
:
ZEND_API zend_class_entry *zend_exception_get_default(TSRMLS_D) /* {{{ */
{
return default_exception_ce;
}
:
void zend_register_default_exception(TSRMLS_D) /* {{{ */
{
zend_class_entry ce;
INIT_CLASS_ENTRY(ce, "Exception", default_exception_functions);
default_exception_ce = zend_register_internal_class(&ce TSRMLS_CC);
:
}
Exceptionクラスは基底クラスなのでzend_register_internal_classを使っていますが、ほぼ同じような手順で登録されているのが分かります。
独自例外クラスのサブクラスを投げるモジュールのCソース

例外を登録するzend_register_internal_class_ex関数の定義は
ZEND_API zend_class_entry *zend_register_internal_class_ex(zend_class_entry *class_entry, zend_class_entry *parent_ce, char *parent_name TSRMLS_DC);
となっていて、親クラスを指定する第2引数はzend_class_entry型のポインタなので、独自例外クラスのサブクラスを作りたい場合は単に
zend_class_entry *exception_test_exception_ce;
zend_class_entry *exception_test_sub_exception_ce;
:
PHP_MINIT_FUNCTION(exception_test)
{
zend_class_entry ce;
INIT_CLASS_ENTRY(ce, "ExceptionTestException", NULL);
exception_test_exception_ce = zend_register_internal_class_ex(&ce, zend_exception_get_default(TSRMLS_C), NULL TSRMLS_CC);
INIT_CLASS_ENTRY(ce, "ExceptionTestSubException", NULL);
exception_test_sub_exception_ce = zend_register_internal_class_ex(&ce, exception_test_exception_ce, NULL TSRMLS_CC);
:
}
にすれば良いだけなんですね。
先ほどのPEAR::CodeGen_PECLのspecファイルを書き換えると、次のような感じです。
<?xml version="1.0"?> <extension name="exception_test" version="0.0.2"> <summary>from exception_test</summary> <description>from exception_test</description> <license>PHP</license> <code position="top"> <![CDATA[ zend_class_entry *exception_test_exception_ce; zend_class_entry *exception_test_sub_exception_ce; ]]> </code> <class name="ExceptionTest"> <function name="throwException"> <proto>boolean throwException()</proto> <code> <![CDATA[ zend_throw_exception_ex(exception_test_exception_ce, 0 TSRMLS_CC, "a Subclass of ExceptionTestException was threw"); RETURN_TRUE; ]]> </code> <test> <code> <![CDATA[ try { $obj = new ExceptionTest(); $obj->throwException(); echo 'NG'; } catch (ExceptionTestSubException $e) { echo 'NG'; } catch (ExceptionTestException $e) { echo (is_subclass_of($e, 'Exception') ? 'OK' : 'NG'); echo (is_subclass_of($e, 'ExceptionTestException') ? 'NG' : 'OK'); } catch (Exception $e) { echo 'NG'; }]]> </code> <result>OKOK</result> </test> </function> <function name="throwSubException"> <proto>boolean throwSubException()</proto> <code> <![CDATA[ zend_throw_exception_ex(exception_test_sub_exception_ce, 0 TSRMLS_CC, "Exception throwd"); RETURN_TRUE; ]]> </code> <test> <code> <![CDATA[ try { $obj = new ExceptionTest(); $obj->throwSubException(); echo 'NG'; } catch (ExceptionTestSubException $e) { echo (is_subclass_of($e, 'Exception') ? 'OK' : 'NG'); echo (is_subclass_of($e, 'ExceptionTestException') ? 'OK' : 'NG'); } catch (ExceptionTestException $e) { echo 'NG'; } catch (Exception $e) { echo 'NG'; }]]> </code> <result>OKOK</result> </test> </function> </class> <function role="internal" name="MINIT"> <code> <![CDATA[ zend_class_entry ce; INIT_CLASS_ENTRY(ce, "ExceptionTestException", NULL); exception_test_exception_ce = zend_register_internal_class_ex(&ce, zend_exception_get_default(TSRMLS_C), NULL TSRMLS_CC); INIT_CLASS_ENTRY(ce, "ExceptionTestSubException", NULL); exception_test_sub_exception_ce = zend_register_internal_class_ex(&ce, exception_test_exception_ce, NULL TSRMLS_CC); ]]> </code> </function> </extension>
