このクラスは、PEAR パッケージ の一部として提供されており、以下のような特徴があります。
単体テストが十分に行なわれており、ドキュメントもきちんと作成されている
動作が機敏 - PEAR_Error をはるかに上回る
パッケージ固有のエラー処理
エラーレベル(notice/warning/error/exception)の指定
エラーに関連するデータがエラーメッセージとは別に保存される
エラーの階層化 - 親エラーを指定可能
エラーメッセージの動的な生成機能により、 同一のエラーオブジェクトに対して異なるエラーメッセージを 生成することが可能
エラーメッセージの生成・エラーコンテキストの生成・ エラー処理機能において、洗練されたコールバック機能が利用可能。 エラーコンテキストの表示, カスタムエラーメッセージの生成, および エラー生成の制御 も参照ください。
PEAR_ErrorStack では、スタック形式でのエラーの生成と処理を実装しています。 この形式は、PEAR_Error の実装形式に比べてはるかに優れています。 PEAR_Error では、エラーの生成やエラーハンドリングを PEAR_Error オブジェクトのコンストラクタで集中管理しています。 ひとたびオブジェクトが生成されたら、ひき続いて、 メソッドの返り値をチェックするか、単一のグローバルなコールバックを用いるかして、 すべてのエラー処理を完了させてやる必要があります。 さらに、PEAR_Error ではエラーの発生元をたどることがほぼ不可能ですし、 エラーの生成の際には、PEAR の基底クラスの大きくて重い一連のメソッドがコールされる ことになります。
<?php // 昔ながらの PEAR_Error の使用法 require_once 'PEAR.php'; class myobj { // $err がどこで発生したのかがわからない function errorCallback($err) { $this->display($err->getMessage()); $this->log($err->getMessage()); } function log($msg) { error_log($msg, 3, 'somefile.log') } function display($msg) { echo $msg . '<br />'; } } $myobj = new myobj; // コールバックを利用する PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, array(&$myobj, 'errorCallback')); $ret = SomePackage::doSomething(); if (PEAR::isError($ret)) { // 何かの処理をする - このエラーは画面に表示され、ログにも記録される } PEAR::pushErrorHandling(PEAR_ERROR_RETURN); $ret = SomePackage::doSomething(); if (PEAR::isError($ret)) { // 何かの処理をする - このエラーは画面にはあらわれないし、ログにも記録されない } PEAR::popErrorHandling(); ?> |
PEAR_ErrorStack クラスは Log パッケージを参考に作られており、 エラーの識別や、さらにはエラーの自動再パッケージも容易に行うことができます。
<?php // PEAR_ErrorStack を用いたエラー処理 require_once 'PEAR/ErrorStack.php'; require_once 'Log.php'; define('MYPACKAGE_ERROR_DBERROR', 1); class myobj { var $_stack; function myobj() { $this->_stack = &PEAR_ErrorStack::singleton('MyPackage'); } function errorCallback($err) { switch($err['package']){ case 'MyPackage': // エラースタックに、エラーのログを残すことだけを // 指定する。スタックには積み込まない。 return PEAR_ERRORSTACK_LOG; break; case 'InternalDbPackage': // エンドユーザにわかりやすいように、これらのエラーを // mypackagefor のエラーとしてパッケージしなおす。 $this->_stack->push(MYPACKAGE_ERROR_DBERROR, 'error', array('dbmessage' => $err['message'], 'dbcode' => $err['code'], 'We are having Connection problems, please' . 'try again in a few moments'), '', $err); // エラーを再パッケージする // 内部の DB エラースタックに、エラーを無視して // 何事もなかったように振舞うように伝える。 return PEAR_ERRORSTACK_IGNORE; break; } // switch } } $myobj = &new myobj; // 自分のパッケージ用と内部 DB パッケージ用にエラースタックを分ける $dbstack = &PEAR_ErrorStack::singleton('InternalDbPackage'); $mystack = &PEAR_ErrorStack::singleton('MyPackage'); // PEAR::Log を用いたログ出力を設定する $log = &Log::Factory('file', 'somefile.log', 'MyPackage error log'); $mystack->setLogger($log); // デフォルトログとして指定し、すべてのエラースタックに利用させる PEAR_ErrorStack::setDefaultLogger($log); // MyPackage で発生したエラーはすべてログに記録される $ret = SomePackage::doSomething(); // どんなエラーであっても $ret をチェックする必要はない - // エラーは完全にコードと分離されている if ($dbstack->hasErrors()) { var_dump($dbstack->getErrors(); } // すべてのエラーに対してのデフォルトのコールバックを設定する PEAR_ErrorStack::setDefaultCallback(array(&$myobj, 'errorCallback')); // これで、すべての DB エラーが透過的に // わかりやすい MyPackage エラーに変換される。 $ret = SomePackage::doSomething(); ?> |
PEAR_Error があるのに、なぜまた新しいエラー処理ルーチンを作ったのでしょうか? PEAR_Error にはいくつかの問題があります。 エラーメッセージがエラークラスに保持されているにもかかわらず、コンピュータに エラーメッセージを自動的に処理させることは困難です。 さらに、 いったん PEAR_Error に保持されてしまったエラーメッセージを 翻訳することも容易ではありません。 また、エラー関連情報をエラークラスに 格納するための標準機能も存在しません。 そのうえ、エラーメッセージ関連の 問題として、PEAR_Error オブジェクト がどのパッケージで 作成されたのかもわかりません。またそのエラーの深刻度もわかりません。 致命的なエラーもそうでないエラーもまったく同じように見えてしまいます。
PEAR_Error オブジェクト の最大の欠陥は、 エラーをすべて同一のものとしてしまう設計です。 すべての PEAR_Error オブジェクト は、ただ単に PEAR_Error オブジェクト であるだけです。 エラーの深刻度や発生元を区別する方法がありません。 深刻度を定義する唯一の方法は、PEAR_ERROR_TRIGGER を指定して、 PHP の trigger_error 関数の 定数 PEAR_ERROR_TRIGGER および E_USER_NOTICE/E_USER_WARNING/E_USER_ERROR を用いることです。 しかし、この機能のために 900 行ものコードを使うのは馬鹿げています。 なぜなら trigger_error() は PHP に組み込まれているからです!
では、新しいエラーオブジェクトを使うために、まずはすべての PEAR::raiseError() や PEAR::throwError() の呼び出しを書き換えましょう。こういうのを、
<?php require_once 'PEAR.php'; // 古いやりかた $error_specific_info = 'bad'; $e = PEAR::raiseError("error message - very " . $error_specific_info . " way to do things", MYPACKAGE_ERROR_FOO); // 別の古いやりかた $e = PEAR::throwError("error message - very " . $error_specific_info . " way to do things", MYPACKAGE_ERROR_FOO); ?> |
こんな風にします。
<?php require_once 'PEAR/ErrorStack.php'; // 新しいやりかた // バージョン 1: スタックインスタンスへのアクセス $stack = &PEAR_ErrorStack::singleton('MyPackage'); $stack->push(MYPACKAGE_ERROR_DBERROR, 'error', array('query' => $query, 'dsn' => $dsn), 'Critical Database Error: Contact Administrator immediately'); // バージョン 2: 静的なシングルトンへのアクセス(若干遅い) PEAR_ErrorStack::staticPush('MyPackage', MYPACKAGE_ERROR_DBERROR, 'error', array('query' => $query, 'dsn' => $dsn), 'Critical Database Error: Contact Administrator immediately'); ?> |
PEAR_Error のかわりに PEAR_ErrorStack パッケージを利用するために 最低限必要なのはこれだけです。
エラーの生成方法をカスタマイズしたいこともあるでしょう。たとえば、 エラーを追跡するためには、 エラーが発生したファイル名・行番号およびクラス名/関数名を含めると 便利です。 デフォルトのオプションは、ほとんどの場合に十分要件を 満たします。これは PEAR_ErrorStack::getFileLine() で得られます。
すべてのエラーが PHP のソースファイル中で発生するとは限りません。 たとえばテンプレートエンジンでのコンパイルエラーはテンプレートの ソースファイル中で発生します。 データベースのエラーは、クエリーの テキストやデータベースの内部で起こることもあります。 インターネットパッケージでは、エラーは別のサーバ上で発生するかも知れません。 これらのすべてのエラー関連情報は、コンテキスト指定コールバック (context grabbing callback) を用いてエラーメッセージに含めることが可能です。
<?php require_once 'PEAR/ErrorStack.php'; class DatabaseClass { var $_dbError; var $_dbErrorMsg; var $_dbQuery; var $_dbPos; /** * データベースパッケージのコンテキスト情報を取得する * @param integer エラーコード * @param array エラーパラメータ情報 {@link PEAR_ErrorStack::push()} * @param array debug_backtrace() の出力(このコールバックでは利用されません) */ function getErrorContext($code, $params, $backtrace) { $context = array( 'errorcode' => $this->_dbError, 'errormsg' => $this->_dbErrorMsg, 'query' => $this->_dbQuery, 'pos' => $this->_dbPos, ); return $context; } } $db = new DatabaseClass; PEAR_ErrorStack::staticSetContextCallback('Database', array(&$db, 'getErrorContext')); ?> |
コンテキスト情報は、外部のアプリケーションからも操作しやすいような 書式となっています。 もしコンテキスト情報をエラーメッセージに 含めたければ、エラーメッセージコールバックを用いて情報を可読形式の エラーメッセージに変換します。この方法については次のセクションで説明します。
エラーメッセージを効率的に生成するために、PEAR_ErrorStack では 3 つの 方法があります。 利用するためには、3 つのうちひとつを実行する必要があります。
PEAR_ErrorStack::setErrorMessageTemplate() をコールし、エラーコードとエラーメッセージテンプレートを関連付けた 配列を設定します。このように。
<?php define('ERROR_ONE', 1); define('ERROR_TWO', 2); define('ERROR_THREE', 3); define('ERROR_FOUR', 4); require_once 'PEAR/ErrorStack.php'; $stack = &PEAR_ErrorStack::singleton('mypackage'); $messages = array( ERROR_ONE => 'The gronk number %num% dropped a %thing%', ERROR_TWO => 'The %list% items were missing', ERROR_THREE => 'I like chocolate, how about %you%?', ERROR_FOUR => 'and a %partridge% in a pear %tree%', ); $stack->setErrorMessageTemplate($messages); ?> |
置換は str_replace を用いて行われ、非常にシンプルです。 基本的に、もしパーセント記号(%) で囲まれた変数名があれば、連想配列で渡された値で置き換えられます。
array('varname' => 'value'); |
さらに、もし値がオブジェクトだった場合は、そのオブジェクトについて "__toString()" という名前のメソッドが あるかどうかを探し、見つかればそれを用いてオブジェクトを文字列に 変換します。 もし文字列の配列だった場合は、それらがカンマ区切りで 連結されます。
<?php array('varname' => array('first', 'second', 'third')); // これは 'first, second, third' となります ?> |
PEAR_ErrorStack::setMessageCallback() をコールし、独自のエラーメッセージを生成するための関数やメソッドを 設定します。 複雑な状況においては、おそらくこれが一番の方法でしょう。 これを利用すると、ユーザは PEAR_ErrorStack::getMessageCallback() でエラーメッセージの上書きや拡張が可能となります。 例。
<?php require_once 'PEAR/ErrorStack.php'; class foo { var $_oldcallback; function callback(&$stack, $err) { $message = call_user_func_array($this->_oldcallback, array(&$stack, $err)); $message .= "File " . $err['context']['file']; return $message; } } $a = new foo; $stack = &PEAR_ErrorStack::singleton('otherpackage'); $a->_oldcallback = $stack->getMessageCallback('otherpackage'); $stack->setMessageCallback(array(&$a, 'callback')); ?> |
PEAR_ErrorStack を継承したクラスを作成し、 PEAR_ErrorStack::getErrorMessageTemplate() あるいは PEAR_ErrorStack::getErrorMessage() をオーバーライドします。 このクラスが別のパッケージ/アプリケーションからも 利用できることを保証するため、このコードをクラス宣言の直後に入れてください。
<?php PEAR_ErrorStack::singleton('mypackage', false, null, 'MyPEAR_ErrorStack'); ?> |
エラー生成のきめこまやかな制御が必要になる状況は多々あります。 一般的なエラー処理コールバック (generic error handling callback) では、 発生するエラーはすべてひとつのコールバックで処理されるようになっています。 PEAR_ErrorStack では個々のパッケージについて別々のコールバックを 利用できますが、 PEAR_ErrorStack::staticPushCallback() メソッドを用いて一般的なエラー処理コールバックを行うことも可能です。 これは、PEAR_Error の PEAR_ERROR_CALLBACK モードと同じです。
PEAR_ErrorStack の真の強みは、このコールバックにあります。 PEAR_Error のコールバックではエラーメッセージに対して変更を加えることができません。 すべてのエラー処理はコールバック関数あるいはメソッドの中で完結する 必要があります。 PEAR_ErrorStack のコールバックでは、3 つの定数を用いて エラー処理を変更することができます。
PEAR_ERRORSTACK_IGNORE はスタックに対し、エラーを無視して 何事も起こらなかったように振る舞わせます。エラーはログに記録されず、 スタックにも保存されません。しかし、 PEAR_ErrorStack::push() からは返されます。
PEAR_ERRORSTACK_PUSH はスタックに対し、エラーを保存するが ログには記録しないことを指示します。
PEAR_ERRORSTACK_LOG はスタックに対し、エラーを保存せずに ログにだけ記録することを指示します。
<?php define('ERROR_CODE_ONE',1); define('ERROR_CODE_TWO',2); define('ERROR_CODE_THREE',3); require_once 'PEAR/ErrorStack.php'; require_once 'Log.php'; function somecallback($err) { switch($err['code']){ case ERROR_CODE_ONE: return PEAR_ERRORSTACK_IGNORE; break; case ERROR_CODE_TWO: return PEAR_ERRORSTACK_PUSH; break; case ERROR_CODE_THREE: return PEAR_ERRORSTACK_LOG; break; } // switch } $log = &Log::factory('display'); $stack = &PEAR_ErrorStack::singleton('mypackage'); $stack->setLogger($log); $stack->pushCallback('somecallback'); $stack->push(ERROR_CODE_ONE); $stack->push(ERROR_CODE_TWO); $stack->push(ERROR_CODE_THREE); var_dump(PEAR_ErrorStack::staticGetErrors()); // simulate PEAR_ERROR_CALLBACK, with specific callback for mypackage // every other package will only log errors, only mypackage's errors // are pushed on the stack, conditionally class myclass { function acallback($err) { return PEAR_ERRORSTACK_LOG; } } $stack2 = PEAR_ErrorStack::singleton('anotherpackage'); $stack3 = &PEAR_ErrorStack::singleton('thirdpackage'); PEAR_ErrorStack::setDefaultCallback(array('myclass', 'acallback')); ?> |
エラーコールバックの最も解りやすい使用法としては、いくつもの ユーザレベルのアプリケーションがシステムレベルのパッケージを利用する場合に よく行われる方法が挙げられます。 たとえば、PEAR DB パッケージを用いたコンテンツ管理システム (CMS)を書いているとしましょう。ユーザがフォーラムへの投稿のために リンクをクリックしたときに、データベースのエラーが表示されるのは あまりよくありません。 このような場合にデータベースエラーを MyPackage のエラーに再パッケージするために PEAR_ErrorStack が用いられます。
<?php define('MYPACKAGE_ERROR_DBDOWN',1); require_once 'PEAR/ErrorStack.php'; function repackage($err) { if ($err['package'] == 'DB') { $mystack = &PEAR_ErrorStack::singleton('mypackage'); $mystack->push(MYPACKAGE_ERROR_DBDOWN, 'error', array('olderror' => $err)); // DB エラーを無視し、mypackage のエラーとして記録する return PEAR_ERRORSTACK_IGNORE; } } ?> |
PEAR_Error の PEAR::expectError() メソッドは、強力ですが 使いにくい面もあります。 通常の PHP のエラーであれば、@ 演算子を以下のように用いることで 出力を抑制することができます。
<?php @file_get_contents(); ?> |
PEAR_ErrorStack でこの動作を再現するのは簡単です。
<?php define('ERROR_CODE_SOMETHING', 1); require_once 'PEAR/ErrorStack.php'; function silence($err) { // すべてのエラーを無視する return PEAR_ERRORSTACK_IGNORE; } $stack = &PEAR_ErrorStack::singleton('mypackage'); $stack->pushCallback('silence'); $stack->push(ERROR_CODE_SOMETHING); ?> |
PEAR_ErrorStack はこれを一歩進め、さらに 2 つの定数を用いることで 「ログにのみ記録する」「スタックにのみ記録する」といった制御が可能です。 最後に、特定のエラーだけを選び出してそれ以外を無視する例を示します。
<?php define('SOMEPACKAGE_ERROR_THING', 1); require_once 'PEAR/ErrorStack.php'; function silenceSome($err) { if ($err['package'] != 'somepackage') { // 他のすべてのパッケージのエラーを無視する return PEAR_ERROR_IGNORE; } if ($err['code'] != SOMEPACKAGE_ERROR_THING) { // 他のすべてのエラーコードを無視する return PEAR_ERRORSTACK_IGNORE; } } $stack = &PEAR_ErrorStack::singleton('mypackage'); $stack->pushCallback('silenceSome'); $stack->push(ERROR_CODE_SOMETHING); ?> |
PEAR_ErrorStack は、 PEAR::raiseError() を用いて自動的に PEAR_Error を生成するようにプログラムすることもできます。そのためには 以下のように PEAR_Error compatibility に true を設定します。
<?php require_once 'PEAR/ErrorStack.php'; $stack = &PEAR_ErrorStack::singleton('mypackage', false, false, true); ?> |
PEAR_ErrorStack は新しい PEAR_Exception クラスとともに使用することが可能です。 このようなコードで、例外を変換します。 以下のコードで、返される例外クラス名を設定することができます。
<?php require_once 'PEAR/ErrorStack.php'; require_once 'PEAR/Exception.php'; $stack = &PEAR_ErrorStack::singleton('mypackage'); $stack->push(1, 'test error'); throw new PEAR_Exception('did not work', $stack->pop()); ?> |