Advanced Error Handling

HTML_Progress

Prev |  | Next
Advanced Error Handling introduction to a new error-handling system ## Authors Laurent 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 Introduction Basic example Advanced features Error Context Display Custom Error Message Generation Controlling error generation Controlling Error Logging Default Display Handler Default Error_Log Handler Ultimate control: Custom Error Handlers ## Introduction 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 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 example Now 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 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 (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 Generation Let'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 generation There 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

    <?php
        function _handleError($err)
        {
            if ($err['level'] == 'exception') {
                return HTML_PROGRESS_ERRORSTACK_LOGANDDIE;
            }}
    ?>
On default behavior, each time an exception is raised, HTML_Progress log the error and halt PHP script execution. ## Controlling Error Logging The 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 Handler The 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 Handler The 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: see http://www.php.net/manual/en/function.strftime.php 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 Handlers For 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
Documentation generated on Sun, 12 Sep 2004 20:22:45 +0200 by phpDocumentor 1.3.0RC3