Saturday, September 03, 2005

Object registry and Observers...

The build on Chris's application is going well and we have finished implementing the core tiered architecture!

The first step was to construct a simple object registry so that the individual components can be shared via a composite pattern. In a nutshell, it is a basic text book Singleton with a twist. The code looks like this:

Singleton object registry class

001 
002 
003 
004 
005 
006 
007 
008 
009 
010 
011 
012 
<?php
class Singleton{
    function &
getInstance ($class, $name = ''){
        static
$registry = array();
        
$_instanceName = notNull($name) ? $name : $class;
        if ( !isset(
$registry[$class][$_instanceName]) || !is_object($registry[$class][$_instanceName]) ) {
            
$registry[$class][$_instanceName] =& new $class;
        }
        return
$registry[$class][$_instanceName];
    }
}
?>

The code above is the backbone of the application since it is completely object oriented. The benefit is that the code (classes or objects) are reusable and are able to be shared among all yet only instantiated once. This adds tremendous flexibility while keeping system resource utilization to a minimum as the registry contains only references to the objects and NOT copies of the objects.

The bottom line is that the category pages are rendering in less than .03 seconds with .005 total MySQL time (of course, no caching yet). Almost forgot...only 5 queries! The performance is blistering fast and infinitely scalable * to the limits of MySQL*. The reason for the outstanding performance is that the application was coded from the bottom up with an eye on performance. Since we are sharing objects we eliminated a lot of redundant database queries and processing overhead with multiple instantiation.

In addition, we have implemented an Observer / Listener pattern. This will form the backbone of the first plug-and-play eCommerce shopping cart! Admittingly, our beta code is far from perfect but has tremendous potential. The idea is the place strategic hooks in the business logic (and maybe objects) which will enable notification to event Listeners. Thus far we have implemented 2 core systems that use the Observer pattern: error logging and also script/performance timers. Here is the base class:

Observer pattern class

001 
002 
003 
004 
005 
006 
007 
008 
009 
010 
011 
012 
013 
014 
015 
016 
017 
018 
019 
020 
021 
022 
023 
024 
025 
026 
027 
028 
029 
030 
031 
032 
033 
034 
035 
036 
037 
038 
039 
040 
<?php
class Observer{
    var
$_observers;
    var
$_message;
    
    function
Observer(){
        
$this->_observers = array();
        
$this->_message = NULL;
    }
# end constructor
    
    
function attach(&$observer){
        
$this->_observers[] =& $observer;
    }
# end function
    
    
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 function
    
    
function notify(){
        foreach (
array_keys($this->_observers) as $key ){
            
$observer =& $this->_observers[$key];
            
$observer->update($this);
        }
# end foreach
    
} # end function
    
    
function getState(){
        return
$this->_message;
    }
# end function
    
    
function setState($info){
        
$this->_message = $info;
        
$this->notify();
    }
# end function    
} # end class
?>

...example instantiation of Observer object and attachment of event hander

001 
002 
003 
004 
005 
006 
007 
008 
009 
010 
011 
012 
013 
014 
015 
016 
017 
018 
019 
020 
021 
022 
023 
024 
025 
026 
027 
028 
029 
<?php
/////////
// This code is in application_top.php
/////////
/*
* Initialize the error handler (observer) class as a Singleton
* and attach the screen output object
*/
    
$errorHandler =& Singleton::getInstance('Observer', 'ErrorHandler');
    
$errorHandler->attach(Singleton::getInstance('ScreenOutputErrorLogger'));

/*
* Set the custom error handler function
*/
    
set_error_handler('observerErrorHandler');

/////////
// This code is a general (callback) function
/////////
  
function observerErrorHandler($errno, $errstr, $errfile, $errline, $errcontext){
      
$handler =& Singleton::getInstance('Observer', 'ErrorHandler');
    
$handler->setState( array('number' => $errno,
                              
'msg' => $errstr,
                              
'file' => $errfile,
                              
'line' => $errline
                              
)
                      );
  }
?>

ScreenOutputErrorLogger event handler class

001 
002 
003 
004 
005 
006 
007 
008 
009 
010 
011 
012 
013 
014 
015 
016 
017 
018 
019 
020 
021 
022 
023 
024 
025 
026 
027 
028 
<?php
class ScreenOutputErrorLogger{
    var
$_errors;
    
    function
ScreenOutputErrorLogger(){
        
$this->_errors = array();
    }
# end constructor
    
    
function update(&$error_handler){
        
$error = $error_handler->getState();
        
$this->_errors[] = $error;
    }
# end function
    
    
function errorCount(){
        return
sizeof($this->_errors);
    }
# end function
    
    
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 function
    
} # end class
?>

The basic operation is that once an Observer is instantiated other objects can register as an event handler. The great benefit is that there can be multiple event handlers for each! So, the ErrorHandler object is instantiated and then directly after that the ScreenOutputErrorLogger is attached. Thus, every non-fatal error that is triggered will send the Listener state to every object that is registered for notification (right now only the screen logger). The flexibility in this system is that other objects can be registered as well. As an example, we will create the email object next which can be registered as an event handler and email the administrator on database errors (instead of killing the script). In fact, we can create as many event handlers as we want. There could easily be a logger event handler that saves the errors to database or text file. The point is that event handlers can be attached and detached as needed without affecting the function of the others.

Another development milestone is the true tiered architecture. We have implemented a classic 4 tier architecture: presentation (template engine), business logic (meat and bones manipulating object interaction), accessor (database access), and persistence (database). We have tested the architecture by placing each tier on a different server that is physically separate from the others. The application performed beautifully and is confirmation the tiered architecture is complete and bug free.

This ground-up build has enabled me to finally implement my custom rolled template engine! There is no PHP in the templates and is completely separate from the code. Thus, inexperienced webmasters can tweak and modify the templates all they want without ever corrupting the core code. In addition, I feel a robust templating engine will afford more designers to quickly create skins. Here is the template class code:

Template engine class

001 
002 
003 
004 
005 
006 
007 
008 
009 
010 
011 
012 
013 
014 
015 
016 
017 
018 
019 
020 
021 
022 
023 
024 
025 
026 
027 
028 
029 
030 
031 
032 
033 
034 
035 
036 
037 
038 
039 
040 
041 
042 
043 
044 
045 
046 
047 
048 
049 
050 
051 
052 
053 
054 
055 
056 
057 
<?php
class Template{
    
    var
$template;
    var
$variables;
    
    function
Template($template = ''){        
        
$this->template = notNull($template) ? file_get_contents($template) : NULL;
    }
# end class constructor
    
    
function Add($var_name, $var_data){
        
$this->variables[$var_name] = $var_data;
    }
# end fucntion Add()

    
function AddMulti($var_data){
        if (
is_array($var_data)){
            foreach (
$var_data as $name => $data ){
                
$this->variables[$name] = $data;
            }
        }
    }
# end fucntion Add()

    
function ResetVars(){
        
$this->variables = NULL;
    }
# end function
    
    
function EvaluateTag($data, $tag, $params = NULL){
        
$_tag = '<' . $tag;
        if (
notNull($params)){
            
$_tag .= ' ' . $params;
        }
        
$_tag .= '>' . $data . '</' . $tag . '>' . "\n";
        return
$_tag;
    }
# end function
    
    
function Remove($vars){
        if (
is_array($vars) ){
            foreach (
$vars as $index => $varname ){
                unset(
$this->variables[$varname] );
            }
# end foreach
        
} else {
            unset(
$this->variables[$vars]);
        }
    }
# end fucntion Add()
    
    
function Evaluate($direct_output = false){
        
$template = addslashes($this->template);        
        foreach (
$this->variables as $variable => $data ){
            $
$variable = $data;
        }
# end foreach        
        
eval("\$template = \"$template\";");        
        if (
$direct_output ) echo stripslashes($template);
        else return
stripslashes($template);        
    }
# end function Evaluate()
    
} # end class
?>

As you can see, we are making good progress on the build and will soon move onto finalizing the beta version of the shopping cart! However, there is still much left to do in order to launch this project. I still need to coordinate the acquisition of a dedicated server to host the project Subversion server. In addition, we need to attract a community to help in the beta testing. It is almost time to launch!

...almost there...stay tuned!