より進んだエラー処理のための PEAR_ErrorStack の利用方法
より進んだエラー処理のための PEAR_ErrorStack の利用方法
–
シンプルで、かつ進んだエラー処理を行うための PEAR_ErrorStack の利用
Synopsis
PEAR_ErrorStack の利用方法の紹介
導入
このクラスは、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
を用いて行われ、非常にシンプルです。
基本的に、もしパーセント記号(%)
で囲まれた変数名があれば、連想配列で渡された値で置き換えられます。
<?php
array('varname' => 'value');
?>
のような配列がメソッドに渡されれば、%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_Error との後方互換性、PHP 5 の例外および PEAR_Exception との前方互換性
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());
?>