Advanced Error Handling
introduction to a new error-handling system
AuthorsLaurent
Laville
Gregory
Beaver
Special thanks to Gregory Beaver, for his works on
PEAR_ErrorStack (part of PEAR core >= 1.3.1) and
its manual,
source of inspiration for the new HTML_Progress error handling system.
Table of Contents
IntroductionWhy 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 object.
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.
HTML_Progress implements error raising and handling using a stack pattern
like PEAR_ErrorStack.
So why don't just use PEAR_ErrorStack rather than rewrite the same concept.
HTML_Progress is not a copy of features of PEAR_ErrorStack,
even it allows to plug-in any error-handler routine you might want to have.
Features of HTML_Progress error handling system include :
- Error levels (notice/warning/error/exception)
- Error context data is saved separate from error message
- Dynamic error message generation
- Sophisticated callbacks are available for error message generation, error context generation, and error handling functionality
- Use your own error-handler
Default error handling system of HTML_Progress allow to :
- Display or not any error message with control of PHP INI display_errors value
- Display error message as the default PHP error handler (Error level and File, Line context in bold face)
Basic exampleNow with this example, we will demonstrate the basic use and result
of HTML_Progress error handler.
- <?php
- require_once 'HTML/Progress.php';
-
- $bar = new HTML_Progress();
-
- $e = $bar->setDM('dm_class_model');
-
- if (is_object($e)) {
- if (is_a($e,'PEAR_Error')) {
- die('<h1>Catch PEAR_Error</h1>'. $e->toString());
- }
- }
- ?>
We will get the message below only if PHP INI display_errors
is enable ('on' | '1' ).
Error: invalid input, parameter #1 "$model" was expecting "dm_class_model class defined", instead got "class does not exists"
in html_progress->setdm
(file d:\php\pear\html_progress\tutorials\html_progress\examples\eh_basic_display.php at line 6)
For basic use, this is all you have to know to use HTML_Progress package
to raise PEAR_Error object without its disadvantages.
Advanced featuresError Context DisplayIn 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 (context_callback)
which will be sufficient for most cases.
Let's have a look on the default context callback routine :
HTML_Progress::_getBacktrace
- <?php
- function _getBacktrace()
- {
- if (function_exists('debug_backtrace')) {
- $backtrace = debug_backtrace(); // PHP 4.3+
- $backtrace = $backtrace[count($backtrace)-1];
- } else {
- $backtrace = false; // PHP 4.1.x, 4.2.x (no context info available)
- }
- return $backtrace;
- }
- ?>
This function generates a PHP backtrace and returns this information as an associative array.
See http://www.php.net/debug_backtrace for details.
If you wish context information to be in the error message, the error handler callback
(option error_handler)
should add the information in a human-readable format to the error message.
Let's have a look on part of the default error callback routine :
HTML_Progress::_errorHandler
- <?php
- function _errorHandler($err)
- {
- include_once 'PEAR.php';
- $e = PEAR::raiseError($err['message'], $err['code'], PEAR_ERROR_RETURN, E_USER_ERROR,
- $err['context']);
-
- if (isset($err['context'])) {
- $file = $err['context']['file'];
- $line = $err['context']['line'];
- $func = $err['context']['class'];
- $func .= $err['context']['type'];
- $func .= $err['context']['function'];
- }
-
- $display_errors = ini_get('display_errors');
- $log_errors = ini_get('log_errors');
-
- $display = $GLOBALS['_HTML_PROGRESS_ERRORHANDLER_OPTIONS']['display'];
-
- if ($display_errors) {
- $lineFormat = $display['conf']['lineFormat'];
- $contextFormat = $display['conf']['contextFormat'];
-
- $context = sprintf($contextFormat, $file, $line, $func);
-
- printf($lineFormat."<br />\n", ucfirst($err['level']), $err['message'], $context);
- }
- if ($log_errors) {
- // more code here ... but hidden
- }
- return $e;
- }
- ?>
Context data are merged into error message with help of lineFormat
and contextFormat configuration options
(see Default Display Handler)
Custom Error Message GenerationLet's have a look on the default message callback routine :
HTML_Progress::_msgCallback
- <?php
- function _msgCallback($err)
- {
- $messages = HTML_Progress::_getErrorMessage();
- $mainmsg = $messages[$err['code']];
-
- if (count($err['params'])) {
- foreach ($err['params'] as $name => $val) {
- if (is_array($val)) {
- $val = implode(', ', $val);
- }
- $mainmsg = str_replace('%' . $name . '%', $val, $mainmsg);
- }}
- return $mainmsg;
- }
-
- function _getErrorMessage()
- {
- $messages = array(
- HTML_PROGRESS_ERROR_INVALID_INPUT =>
- 'invalid input, parameter #%paramnum% '
- . '"%var%" was expecting '
- . '"%expected%", instead got "%was%"',
- HTML_PROGRESS_ERROR_INVALID_CALLBACK =>
- 'invalid callback, parameter #%paramnum% '
- . '"%var%" expecting %element%,'
- . ' instead got "%was%" does not exists',
- HTML_PROGRESS_DEPRECATED =>
- 'method is deprecated '
- . 'use %newmethod% instead of %oldmethod%'
-
- );
- return $messages;
- }
- ?>
HTML_Progress::_getErrorMessage set and return an array mapping error codes
to error message templates.
Substitution is done (line 12) using http://www.php.net/str_replace.
Basically, if a variable name is enclosed in percent sign (%), it will be replaced
with the value passed in the associative array (line 8).
Controlling error generationThere are many scenarios in which fine-grained control over error raising is absolutely necessary.
We can influence the error management through the use of five constants:
- HTML_PROGRESS_ERRORSTACK_PUSHANDLOG
informs the stack to push the error onto the error stack, and also to log the error.
- HTML_PROGRESS_ERRORSTACK_PUSH
informs the stack to push the error onto the error stack, but not to log the error.
- HTML_PROGRESS_ERRORSTACK_LOG
informs the stack not to push the error onto the error stack, but only to log the error.
- HTML_PROGRESS_ERRORSTACK_IGNORE
informs the stack to ignore the error, as if it never occured. The error will be
neither logged, nor push on the stack. However, a PEAR_Error object will be returned
from HTML_Progress::raiseError.
- HTML_PROGRESS_ERRORSTACK_LOGANDDIE
informs the stack not to push the error onto the error stack, but only to log the error
and stop the script.
For example, in HTML_Progress Unit Tests: we don't want that processes stop when
an exception is raised.
Let's have a look on the script HTML_Progress_TestCase_setIndeterminate.php
into the tests directory of this package:
- <?php
-
- class HTML_Progress_TestCase_setIndeterminate extends PHPUnit_TestCase
- {
- /**
- * HTML_Progress instance
- *
- * @var object
- */
- var $progress;
-
- function HTML_Progress_TestCase_setIndeterminate($name)
- {
- $this->PHPUnit_TestCase($name);
- }
-
- function setUp()
- {
- error_reporting(E_ALL & ~E_NOTICE);
-
- $logger['push_callback'] = array(&$this, '_pushCallback'); // don't die when an exception is thrown
- $this->progress = new HTML_Progress($logger);
- }
-
- function tearDown()
- {
- unset($this->progress);
- }
-
- function _stripWhitespace($str)
- {
- return preg_replace('/\\s+/', '', $str);
- }
-
- function _methodExists($name)
- {
- if (substr(PHP_VERSION,0,1) < '5') {
- $n = strtolower($name);
- } else {
- $n = $name;
- }
- if (in_array($n, get_class_methods($this->progress))) {
- return true;
- }
- $this->assertTrue(false, 'method '. $name . ' not implemented in ' . get_class($this->progress));
- return false;
- }
-
- function _pushCallback($err)
- {
- // don't die if the error is an exception (as default callback)
- return HTML_PROGRESS_ERRORSTACK_PUSH;
- }
-
- function _getResult()
- {
- if ($this->progress->hasErrors()) {
- $err = $this->progress->getError();
- $this->assertTrue(false, $err['message']);
- } else {
- $this->assertTrue(true);
- }
- }
-
-
- /**
- * TestCases for method setIndeterminate.
- *
- */
- function test_setIndeterminate_fail_no_boolean()
- {
- if (!$this->_methodExists('setIndeterminate')) {
- return;
- }
- $this->progress->setIndeterminate('');
- $this->_getResult();
- }
-
- function test_setIndeterminate()
- {
- if (!$this->_methodExists('setIndeterminate')) {
- return;
- }
- $this->progress->setIndeterminate(true);
- $this->_getResult();
- }
- }
- ?>
Lines 21 and 22, replace the default error handling (push_callback)
by our own function (method _pushCallback of the class HTML_Progress_TestCase_setIndeterminate).
Into our new push callback (lines 49 to 53) we informs the stack to always push error,
and never die for an exception (default behavior).
Let's have a look on the default push callback routine :
HTML_Progress::_handleError
On default behavior, each time an exception is raised, HTML_Progress log the error
and halt PHP script execution.
Controlling Error LoggingThe next level of control over error output is to selectively log destination. In the default
behavior error_handler callback, that is
HTML_Progress::_errorHandler, will :
- return a PEAR_Error object (E_USER_ERROR level, with error code, message, and
context as userinfo)
- log error to browser screen
(if allowed by PHP INI display_errors value)
- log error following rules of http://www.php.net/manual/en/function.error-log.php
(if allowed by PHP INI log_errors value)
Default Display HandlerThe default display handler has one parameter and two configuration options :
Parameter |
Default value |
conf |
see array below |
Option |
Default value |
lineFormat |
'<b>%1$s</b>: %2$s %3$s' |
contextFormat |
' in <b>%3$s</b> (file <b>%1$s</b> at line <b>%2$s</b>)' |
with lineFormat parameters:
- 1$ = error level
- 2$ = error message
- 3$ = context line formatted
with contextFormat parameters:
- $1 = filename
- $2 = line in file
- $3 = class call-type function
See http://www.php.net/manual/en/function.debug-backtrace.php for call-type details.
Default Error_Log HandlerThe default error_log handler has three parameters and five configuration options :
Parameter |
Default value |
name |
|
ident |
|
conf |
see array below |
with name parameter:
- HTML_PROGRESS_LOG_TYPE_SYSTEM
message is sent to PHP's system logger, using the Operating System's system logging mechanism
or a file, depending on what the error_log configuration directive (PHP INI) is set to.
- HTML_PROGRESS_LOG_TYPE_MAIL
message is sent by email to the address in the destination parameter.
This is the only message type where the fourth parameter, extra_headers is used.
This message type uses the same internal function as mail() does.
- HTML_PROGRESS_LOG_TYPE_FILE
message is appended to the file destination.
Option |
Default value |
destination |
|
extra_headers |
|
lineFormat |
'%1$s %2$s [%3$s] %4$s %5$s' |
timeFormat |
'%b %d %H:%M:%S' |
contextFormat |
' in %3$s (file %1$s at line %2$s)' |
with lineFormat parameter:
- 1$ = error time from time line formatted
- 2$ = error handler ident
- 3$ = error level
- 4$ = error message
- 5$ = context line formatted
with timeFormat parameter:
with contextFormat parameter:
- $1 = filename
- $2 = line in file
- $3 = class call-type function
See http://www.php.net/manual/en/function.debug-backtrace.php for call-type details.
Ultimate control: Custom Error HandlersFor most of users, the basic and default HTML_Progress error handling system will be enough.
But if you want more efficiency, you should set a custom error handler.
Let's take an example and follow it to see how to do :
- <?php
- require_once 'HTML/Progress.php';
-
- function _pushCallback($err)
- {
- // now don't die if the error is an exception, it will be ignored
- if ($err['level'] == 'exception') {
- return HTML_PROGRESS_ERRORSTACK_IGNORE;
- }
- }
- function _errorHandler($err)
- {
- global $options;
-
- $display_errors = ini_get('display_errors');
-
- if ($display_errors) {
- $lineFormat = $options['lineFormat'];
- $contextFormat = $options['contextFormat'];
-
- $file = $err['context']['file'];
- $line = $err['context']['line'];
- $func = $err['context']['class'];
- $func .= $err['context']['type'];
- $func .= $err['context']['function'];
-
- $context = sprintf($contextFormat, $file, $line, $func);
-
- printf($lineFormat."<br />\n", ucfirst($err['level']), $err['message'], $context);
- }
- }
- $logger['push_callback'] = '_pushCallback';
- $logger['error_handler'] = '_errorHandler';
-
- $options = array(
- 'lineFormat' => '<b>%1$s</b>: %2$s <hr>%3$s',
- 'contextFormat' => '<b>Function</b>: %3$s<br/><b>File</b>: %1$s<br/><b>Line</b>: %2$s'
- );
- $logger['handler']['display'] = array('conf' => $options);
-
- $bar = new HTML_Progress($logger);
- $e = $bar->setAnimSpeed('100'); // < - - - will generate an API exception
-
- if (is_object($e)) {
- if (is_a($e,'PEAR_Error')) {
- die('<h1>Catch PEAR_Error API exception</h1>'. $e->toString());
- }
- }
- if (HTML_Progress::hasErrors()) {
- $err = HTML_Progress::getError();
- echo '<pre>';
- print_r($err);
- echo '</pre>';
- die('<h1>Catch HTML_Progress exception</h1>');
- }
-
- $e = $bar->setAnimSpeed(10000); // < - - - will generate an API error
-
- if (is_object($e)) {
- if (is_a($e,'PEAR_Error')) {
- die('<h1>Catch PEAR_Error API error</h1>'. $e->toString());
- }
- }
- if (HTML_Progress::hasErrors()) {
- $err = HTML_Progress::getError();
- die('<h1>Catch HTML_Progress error</h1>'.$err['message']);
- }
- ?>
Lines 4 to 10 we defined our 'push_callback' routine
to ignore all exception error.
Lines 11 to 29 we defined our custom 'error_handler' routine.
Line 39, only the display handler is defined with configuration options
'lineFormat' and 'contextFormat'.
Line 41, we informs the new instance of HTML_Progress
to use this custom error handler.
Line 42, raise an exception that is ignored and code line 44 thru 55
do nothing.
Next (line 57) raise a basic error that return NULL.
So lines 59 to 63 are ignored, and only lines 64 to 67 do their job.
Finally result of this process give on web standard output (browser) :
Error: invalid input, parameter #1 "$delay" was expecting "less or equal 1000", instead got "10000"
---------------------------------------------------------------------------------------------------
Function: html_progress->setanimspeed
File: d:\php\pear\html_progress\tutorials\html_progress\examples\display_errors-p6.php
Line: 57
Catch HTML_Progress error
invalid input, parameter #1 "$delay" was expecting "less or equal 1000", instead got "10000"
Prev |
Up |
Next |
Progress Handler |
Getting Started |
Using Indeterminate Mode |
|
|