PHP4/MySQL Error Handling with an Observer design pattern

Article Introduction

Errors generated by a PHP script are usually output directly to the screen for every web visitor to see. This presents possible security issues by providing clues about site or database structure while also being aesthetically unattractive.

The intent of this article is to demonstrate the principles of implementing a simple Observer pattern to handle reporting for both PHP and MySQL generated non-fatal errors. This allows the application developer the ability to attach (or detach) as many or few clients as desired.

Introduction to the Singleton design pattern

The backbone of the implementation is to always use the same instantiated Observer object which must be accessible globally (in procedural scripts, functions, and other other objects) without the need for global declarations. The Singleton pattern is kind of like an object phone book in that it stores references to the objects registered...not actual copies of the objects. Thus, the Singleton pattern is ideal for this requirement! Let's take a look at a typical Singleton class:

Singleton Class

<?php
class Singleton{
    function &
getInstance ($class, $name = NULL){
        static
$registry = array();
        
$_instanceName = is_null($name) ? $name : $class;
        if ( !isset(
$registry[$class][$_instanceName]) || !is_object($registry[$class][$_instanceName]) ) {
            
$registry[$class][$_instanceName] =& new $class;
        }
        return
$registry[$class][$_instanceName];
    } # end method     

}
?>

It is important to note that PHP4 does not store references in static variables however it does so just fine in static arrays. Before we dissect the class allow me to concede that the getInstance() method could easily be a generally accessible function but I prefer to use it as a class in case I need to extend it for reuse in another application. With that said, let's take a look at the method anatomy:

Singleton class anatomy

function &getInstance ($class, $name = NULL){
This method is designed to return an object reference (notice the "&" symbol in front of the method name) that has been already instantiated OR created and registered. The optional parameter "$name" can be used to reference a particular instance of an object when more than one are available.
static $registry = array();
This "$registry" static array container is simply initialized. The static array is persistent from method call to method call for the life of the script.
$_instanceName = is_null($name) ? $name : $class;
The "$_instanceName" variable is used to either set the generic or specific object reference.
if ( !isset($registry[$class][$_instanceName]) || !is_object($registry[$class][$_instanceName]) ) {
     
$registry[$class][$_instanceName] =& new $class;
}
This block of code checks if the object is not set or if it is not an object yet . If either one of those checks are true it will instantiate the object and store the reference in the "$registry" container.
return $registry[$class][$_instanceName];
This line of code simply returns the object reference stored in the "$registry" container.

Introduction to the Observer design pattern

The Observer design pattern allows an object to communicate state changes to other objects. When the state changes in the subject all of the clients are notified of the state change. It is then up to the clients to take appropriate action. Let's take a look at a typical Observer class:

Observer class

<?php
class Observer{
    var
$_observers;
    var
$_message;
    
    function
Observer(){
        
$this->_observers = array();
        
$this->_message = NULL;
    }
# end constructor
    
    
function attach(&$observer){
        
$this->_observers[] =& $observer;
    }
# end method
    
    
function detach(&$observer){
        foreach (
array_keys($this->_observers) as $key ){
            if (
$this->_observers[$key] === $observer){
                unset(
$this->_observers[$key]);
                return;
            }
# end if
        
} # end foreach
    
} # end method
    
    
function notify(){
        foreach (
array_keys($this->_observers) as $key ){
            
$client=& $this->_observers[$key];
            
$client->update($this);
        }
# end foreach
    
} # end method
    
    
function getState(){
        return
$this->_message;
    }
# end method
    
    
function setState($info){
        
$this->_message = $info;
        
$this->notify();
    }
# end method     
} # end class
?>

I won't dissect the class but will illustrate its typical use:

For your reference below is the code that I use as a client to capture and process the state changes for errors. Don't worry...it may seem like a lot of code to take in but I'll demonstrate how all the pieces fit together.

Observer client class

<?php
class ScreenOutputErrorLogger{
    var
$_errors;
    
    function
ScreenOutputErrorLogger(){
        
$this->_errors = array();
    }
# end constructor
    
    
function update(&$error_handler){
        
$error = $error_handler->getState();
        
$this->_errors[] = $error;
    }
# end method
    
    
function errorCount(){
        return
sizeof($this->_errors);
    }
# end method
    
    
function output($hidden = false){
        if (
$hidden) echo '<!--' . "\n";
        echo
'<h1>Errors:</h1>' . "\n";
        echo
'<pre>' . "\n";
        
print_r($this->_errors);
        echo
'</pre>' . "\n";
        if (
$hidden) echo '//-->' . "\n";
    }
# end method     
} # end class
?>

Putting the pieces together

Now that we have the core code in place we begin assembling the objects. Reference the code below for a sample script:

Procedural script

<?php
////
// Include the classes
// Should be at the top of the script!
    
include_once('classes/Observer.class.php');
    include_once(
'classes/ScreenOutputErrorLogger.class.php');

////
// Initialize the Observer subject with the Singleton registry
// Should be at the top of the script!
    
$errorHandler =& Singleton::getInstance('Observer', 'ErrorHandler');

////
// Attach the SreenOutputErrorLogger object as an observer client
// Should be at the top of the script!
    
$errorHandler->attach(Singleton::getInstance('ScreenOutputErrorLogger'));

////
// Set the custom error handler function
// Should be at the top of the script!
    
set_error_handler('observerErrorHandler');

////
// Define the error handler callback function
// Should be with the rest of the application functions!
  
function observerErrorHandler($errno, $errstr, $errfile, $errline, $errcontext){
      
////
    // Get the ErrorHandler subject object
    
$handler =& Singleton::getInstance('Observer', 'ErrorHandler');
    
////
    // Set the state for the subject
    // The setState() method calls the notify() method
    // which updates all the clients
    
$handler->setState( array('number' => $errno,
                              
'msg' => $errstr,
                              
'file' => $errfile,
                              
'line' => $errline
                              
)
                      );
  }
# end function
  

  /*============================*\
      Business logic of script
  \*============================*/
        
    
function Work($finished = false){
        
$doSomeWork = true;
        if (
$finished === false){
            return
Work(false);
        }
    }
    
    
$myJob = Work(false);

  
/*==============================*\
     End business logic of script
  \*==============================*/


////
// Initialize and get the client object reference
// Should be somewhere near the bottom of the script!
    
$screenOutput =& Singleton::getInstance('ScreenOutputErrorLogger');
    if (
$screenOutput->errorCount() > 0){
        
$screenOutput->output();
    }
?>

Let's take a closer look at the parts of the script and see what does what:

Procedural script anatomy

    include_once('classes/Observer.class.php');
    include_once(
'classes/ScreenOutputErrorLogger.class.php');
As always you must first include the classes before they are instantiated!
    $errorHandler =& Singleton::getInstance('Observer', 'ErrorHandler');
This code instantiates the Observer subject "ErrorHandler". Once it is instantiated other objects can be attached so that any change in state is conveyed to all the clients.
    $errorHandler->attach(Singleton::getInstance('ScreenOutputErrorLogger'));
This code attaches the "ScreenOutputErrorLogger" object with a Singleton instantiation. Now that it is attached as a client any change in state of the subject will be communicated to the update() method.
    set_error_handler('observerErrorHandler');
This line of code overrides the default PHP4 error handler and sets it to the "observerErrorHandler" function.
  function observerErrorHandler($errno, $errstr, $errfile, $errline, $errcontext){
      
////
    // Get the ErrorHandler subject object
    
$handler =& Singleton::getInstance('Observer', 'ErrorHandler');
    
////
    // Set the state for the subject
    // The setState() method calls the notify() method
    // which updates all the clients
    
$handler->setState( array('number' => $errno,
                              
'msg' => $errstr,
                              
'file' => $errfile,
                              
'line' => $errline
                              
)
                      );
  }
# end function

This function is the callback that was passed by the set_error _handler() above. On each non-fatal error triggered it will pass it off to this function.

The first thing that must be done is to get the "ErrorHandler " subject object. Once that is done the setState() method is called with the parameters that were passed. The setState() method of the subject object sets the state and calls the notify() method which in turn calls the update() method of each client attached.

    $screenOutput =& Singleton::getInstance('ScreenOutputErrorLogger');
    if (
$screenOutput->errorCount() > 0){
        
$screenOutput->output();
    }
This code should be placed somewhere near the bottom of the script and will output the errors triggered on that page.

Using the code to catch MySQL errors

The code is already in place to catch non-fatal PHP errors and output them to the screen. There are only a few small changes to existing code to extend the functionality to MySQL functions as well.

<?php
////
// Original code
  
$query = mysql_query($sql);

////
// Revised code to catch and report error
  
$query = mysql_query($sql) or trigger_error('MySQL-' . mysql_errno() . ': ' . mysql_error());
?>

Now that you have the trigger_error() in place if there is one encountered it will pass it off to the callback function observerErrorHandler().

Using the code for development debugging

The code is already in place! The only thing that must be done is trigger an error. Here is an example:

<?php
$var
= 1;
trigger_error('Debug: The value of $var is ' . $var);
?>