PEAR_ErrorStack implements error raising and handling using a stack pattern.
This has tremendous advantages over the PEAR_Error Implementation.
PEAR_Error centralizes all error creation and handling in the
constructor of the PEAR_Error object.
Once an object has been created, all handling must have been completed,
either through checking the return value of a method,
or through a single global callback.
In addition, it is nearly impossible to determine the source of an error,
and the baggage of all of the PEAR base class's bulky,
slow methods accompanies every error creation.
<?php
// traditional PEAR_Error usage
require_once 'PEAR.php';
class myobj
{
// there is no way to know where $err comes from
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;
// using a callback
PEAR::setErrorHandling(PEAR_ERROR_CALLBACK, array(&$myobj, 'errorCallback'));
$ret = SomePackage::doSomething();
if (PEAR::isError($ret)) {
// do some handling - this error is also displayed and logged
}
PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
$ret = SomePackage::doSomething();
if (PEAR::isError($ret)) {
// do some handling - this error is not displayed or logged
}
PEAR::popErrorHandling();
?>
The PEAR_ErrorStack class has built in knowledge of the
Log package,
can easily differentiate and even automatically re-package errors
with little to no difficulty.
<?php
// PEAR_ErrorStack error handling
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':
// tell the error stack to log the error only
// it will not be pushed onto the stack
return PEAR_ERRORSTACK_LOG;
break;
case 'InternalDbPackage':
// re-package these errors as a mypackage error fit
// for enduser consumption
$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); // include the error as re-packaged
// tell the internal DB error stack to ignore this error,
// as if it never happened
return PEAR_ERRORSTACK_IGNORE;
break;
} // switch
}
}
$myobj = &new myobj;
// separate error stacks for my package, and the internal DB package
$dbstack = &PEAR_ErrorStack::singleton('InternalDbPackage');
$mystack = &PEAR_ErrorStack::singleton('MyPackage');
// set up a file log using PEAR::Log
$log = &Log::Factory('file', 'somefile.log', 'MyPackage error log');
$mystack->setLogger($log);
// set up a default log to use for all error stacks
PEAR_ErrorStack::setDefaultLogger($log);
// any errors returned by MyPackage are logged
$ret = SomePackage::doSomething();
// Note that $ret need not be checked for any error condition - errors are
// totally separate from code
if ($dbstack->hasErrors()) {
var_dump($dbstack->getErrors();
}
// sets a default callback for all errors
PEAR_ErrorStack::setDefaultCallback(array(&$myobj, 'errorCallback'));
// any db errors are transparently repackaged as
// user-friendly MyPackage errors now
$ret = SomePackage::doSomething();
?>
Why write a new error-handling routine when
PEAR_Error
already exists?
There are several problems with PEAR_Error.
Although an error message is present in an error class,
processing this error message automatically is excessively
difficult for computers.
In addition, the error message cannot easily be translated once it has been placed into
the PEAR_Error.
There is also no standard facility for
storing error-related data in the error class.
On top of error message-related issues,
there is no way to automatically determine which package a
PEAR_Error object comes from,
or the severity of an error.
Fatal
errors look exactly the same as non-fatal errors.
The largest flaw with PEAR_Error object is the
single-error type design.
Every PEAR_Error object is just a
PEAR_Error object.
There is no differentiating between the severity of an error, or its origin.
The only way to determine the severity is to use
PEAR_ERROR_TRIGGER and E_USER_NOTICE/E_USER_WARNING/E_USER_ERROR
constants from php's
trigger_error.
But using this functionality does not justify 900 lines of
code, simply because trigger_error() is built into PHP itself!
Now, to start using your newly created error objects,
change all of your
PEAR::raiseError()
or PEAR::throwError()
calls from this...
<?php
require_once 'PEAR.php';
// old way:
$error_specific_info = 'bad';
$e = PEAR::raiseError("error message - very " . $error_specific_info .
" way to do things", MYPACKAGE_ERROR_FOO);
// another old way:
$e = PEAR::throwError("error message - very " . $error_specific_info .
" way to do things", MYPACKAGE_ERROR_FOO);
?>
For basic use, this is all that is needed to use the
PEAR_ErrorStack package in place of PEAR_Error.
Advanced Features
Error Context Display
In some cases, you may want to customize error generation.
For instance, for many exceptions, it is useful to include file,
line number,
and class/function context information in order to trace an error.
A default option is available which will be sufficient for most cases,
and that is
PEAR_ErrorStack::getFileLine().
Not all package errors occur in the PHP source file.
For instance, compiling template
engines errors can occur in the template source files.
Database errors can occur in the text of a query,
or internal to the database server.
Internet package errors can occur on another server.
All of this information can be included in an error
message using a context grabbing callback.
<?php
require_once 'PEAR/ErrorStack.php';
class DatabaseClass
{
var $_dbError;
var $_dbErrorMsg;
var $_dbQuery;
var $_dbPos;
/**
* Context grabber for the Database package
* @param integer Error Code
* @param array Error parameters passed into {@link PEAR_ErrorStack::push()}
* @param array Output of debug_backtrace() (not used in this callback)
*/
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'));
?>
The context information is formatted to be easily
processed by an external application.
If you wish context information to be in the error message,
the error message callback should be used to add the information
in a human-readable format to the error message, as described
in the next section.
Custom Error Message Generation
There are three methods of PEAR_ErrorStack designed for
use with generating error messages efficiently.
To use them, you must do one of three things:
<?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);
?>
Substitution is done using
str_replace,
and is very simple.
Basically, if a variable name is enclosed in percent signs (%), it will
be replaced with the value passed in the associative array.
If an array
array('varname' => 'value');
is passed to either method, all occurrences of %varname% will be replaced
by value.
In addition, if values are objects, the methods will search for a method
named "__toString()" and if found,
will use that to convert the object to a string.
If an array of strings is passed in, they will be joined by commas.
<?php
array('varname' => array('first', 'second', 'third'));
// this will become 'first, second, third'
?>
Call
PEAR_ErrorStack::setMessageCallback(),
and set a custom error message generating function or method.
This is probably the best option for the majority of complex situations,
as it allows users to override or even extend the existing error
message callback using
PEAR_ErrorStack::getMessageCallback().
For example:
<?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'));
?>
There are many scenarios in which fine-grained control over error raising
is absolutely necessary.
A generic error handling callback means that every single error
raised will be handled in the same error callback.
Although PEAR_ErrorStack is designed to operate with independent
callbacks for each package, generic error handling is possible through the
PEAR_ErrorStack::staticPushCallback()
method.
This is no different from PEAR_Error's
PEAR_ERROR_CALLBACK error handling mode.
PEAR_ErrorStack's real strength comes from the callback itself.
PEAR_Error's callback has no actual effect on the error message -
all error handling must happen in the callback method or function itself.
PEAR_ErrorStack's callback can influence the error
through the use of three constants:
PEAR_ERRORSTACK_IGNORE informs the stack to ignore the error, as if it never occurred. The error will be
neither logged, nor pushed on the stack. It will, however, be returned from PEAR_ErrorStack::push()
PEAR_ERRORSTACK_PUSH informs the stack to push the error onto the error stack, but not to log the error.
PEAR_ERRORSTACK_LOG informs the stack not to push the error onto the error stack, but only to log the error.
<?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'));
?>
Repackaging errors from one package to another
The most obvious usage for an error callback involves a common scenario
in many user-level applications that use system-level packages.
If you write a Content Management System (CMS) with the PEAR DB package,
it is usually a bad idea to display database-level errors when a user
clicks on a link to add a message to a forum.
PEAR_ErrorStack can be used to repackage this error as a MyPackage error.
<?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));
// ignore the DB error, but save it in the mypackage error, for logging
return PEAR_ERRORSTACK_IGNORE;
}
}
?>
Emulating the @ operator
One of the difficult-to-use strengths of PEAR_Error involves the
PEAR::expectError()
method.
With regular PHP errors, it is possible to silence them using
the @ operator like so:
<?php
@file_get_contents();
?>
Emulating this behavior with PEAR_ErrorStack is simple.
PEAR_ErrorStack can take this one step further,
and only log errors or only put errors on the error stack,
using the other two constants.
Finally, particular errors can be singled out, and all others ignored.
<?php
define('SOMEPACKAGE_ERROR_THING', 1);
require_once 'PEAR/ErrorStack.php';
function silenceSome($err)
{
if ($err['package'] != 'somepackage') {
// ignore all errors from other packages
return PEAR_ERROR_IGNORE;
}
if ($err['code'] != SOMEPACKAGE_ERROR_THING) {
// ignore all other error codes
return PEAR_ERRORSTACK_IGNORE;
}
}
$stack = &PEAR_ErrorStack::singleton('mypackage');
$stack->pushCallback('silenceSome');
$stack->push(ERROR_CODE_SOMETHING);
?>
Backwards compatibility with PEAR_Error,
Forward compatibility with PHP 5 Exception
PEAR_ErrorStack
can also be programmed to automatically raise a PEAR_Error using
PEAR::raiseError(),
simply pass in true to the PEAR_Error compatibility like so: