SitePoint Sponsor

User Tag List

Results 1 to 5 of 5
  1. #1
    SitePoint Guru aamonkey's Avatar
    Join Date
    Sep 2004
    Location
    kansas
    Posts
    953
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    Logging using Observer

    I was reading up on the Observer pattern, and decided it would be a perfect fit for my logging needs, which are currently pretty basic. My first implementation of the pattern is with my Email class (just your basic mime-mail class that uses the mail() function), in which I need to a) keep a 'hard-copy' record of emails sent, and b) document suspected contact form abuse and general mail() errors. These are currently all thrown in one log file.

    I am using your typical, generic 'Observable' and 'Observer' interfaces, which I plan to implement in all my other classes. I decided to do this rather than make separate classes for each implementation of the pattern...we'll see how that goes...I'm also using pull instead of push here--i.e. the update() method simply passes the object to the observer which is responsible for figuring out what it wants to do with it. This seemed the most reasonable way to do this. However I have read through a few discussions that seem to favor pushing instead, so ?

    Last note--my 'Logger' class currently only writes data in one way. That's all I have a need for now, but down the road when I need to write several different types of log formats, I plan to make the Logger abstract and extend it.

    Just looking for any thoughts/criticisms/potential pitfalls with the way I've structured things.

    Code:


    PHP Code:
    <?php


    interface Observable {

        public function 
    attach(Observer $observer);
        public function 
    detach(Observer $observer);
        public function 
    notify();

    }


    interface 
    Observer {

        public function 
    update(Observable $obs);

    }



    class 
    Email implements Observable {

        private 
    $observers        = array();
        private 
    $statusCode        0;
        
    // more vars here
        
        /*    STATUS CODES
            
            0    email sent without issues
            1    email could not be sent (mail() returned false)
            2    bad header value, suspected spam, mail sent anyway
            3    bad header value, suspected spam, mail not sent
            
        */
        

        /*
            snip--class methods here
        */

        
    public function send()
        {
            
    // ....
            // ....
            // sets the status code and sends the email if no errors

            
    $this->notify();

        }

        
        public function 
    attach(Observer $obs)
        {
            
    $this->observers["$obs"] = $obs;
        }
        
        public function 
    detach(Observer $obs)
        {
            unset(
    $observers["$obs"]);

        }
        
        public function 
    notify()
        {
            foreach (
    $this->observers as $obs) {
                
    $obs->update($this);
            }
        }
        

    }



    class 
    EmailLogger implements Observer {

        private 
    $statusCode;
        private 
    $loggerName    'Email';
        

        public function 
    update(Observable $email)
        {
            if (!
    $email instanceof Email) { // must be passed an Email object
                
    throw new Exception('EmailLogger was not passed an "Email" object.');
            }
            
            
    $this->statusCode $email->getStatusCode();


            switch(
    $this->statusCode)
            {
                case 
    0:

                    
    $eventDescription  "MAIL FINE -- statusCode (".$this->statusCode.")";
                    
    $txt $this->buildMailDescription($email);
                    
                    
    $logger = new Logger($this->loggerName);
                    
    $logger->logEvent($eventDescription$txt);
                    
                    break;
                    
                case 
    1:
                
                    
    $eventDescription  "ERROR:  mail() returned false -- statusCode (".$this->statusCode.")";
                    
    $txt $this->buildMailDescription($email);
                    
                    
    $logger = new Logger($this->loggerName);
                    
    $logger->logEvent($eventDescription$txt);
                    
                    break;
                    
                
    /*
                    ..... switch statement continues ...
                */
            
    }
            
        }
        
        
        private function 
    buildMailDescription(Email $email)
        {
            
    $emailDesc  "TO:\t\t".implode(','$email->getToArray())."\n";
            
    $emailDesc .= "CC:\t".implode(','$email->getCcArray())."\n";
            
    $emailDesc .= "BCC:\t".implode(','$email->getBccArray())."\n";
            
    $emailDesc .= "FROM:\t".$email->getFrom()."\n\n";
            
    $emailDesc .= "SUBJECT:\t".$email->getSubject()."\n\n";
            
    $emailDesc .= "MESSAGE:\t".$email->getMessage()."\n\n";
            
            return 
    $emailDesc;

        }

    }


    class 
    Logger {

        private 
    $logFileName,
                
    $logFilePath,
                
    $logFileExt;    
                    
        public function 
    __construct(    $logFileName,
                                        
    $logFilePath=LOG_FILE_PATH,
                                        
    $logFileExt=LOG_FILE_EXT        )
        {
            
    $this->logFileName    $logFileName;
            
    $this->logFilePath    $logFilePath;
            
    $this->logFileExt    $logFileExt;
            
        }


        public function 
    logEvent($eventDescription$txt)
        {
            if (!
    is_dir($this->logFilePath)) {
            
                throw new 
    Exception('Logger error:  log file directory not found ('.$fullPath.')');
            }
            
            
    $fullPath $this->logFilePath.$this->logFileName.$this->logFileExt;
            
            
            
    $writeTxt  "*******************************************************\n";
            
    $writeTxt .= "EVENT DESCRIPTION:\t".$eventDescription."\n\n";
            
    $writeTxt .= "*******************************************************\n";
            
    $writeTxt .= "\tIP:\t\t\t".$_SERVER['REMOTE_ADDR']."\n";
            
    $writeTxt .= "\tHOST:\t\t".gethostbyaddr($_SERVER['REMOTE_ADDR'])."\n";
            
    $writeTxt .= "\tDATE:\t\t".date('m/d/Y H:i:s')."\n\n";
            
    $writeTxt .= "-------------------------------------------------------\n";
            
    $writeTxt .= $txt."\n\n";

            if (!
    file_put_contents($fullPath$writeTxtFILE_APPEND)) { return false; }
            
            return 
    true;
        
        }

    }


    // example usage

    $email = new Email('plain');
    $email->attach(new EmailLogger);
    $email->setFrom('me@example.com');
    // snip...more setters
    $email->send();

    ?>

  2. #2
    SitePoint Wizard silver trophy kyberfabrikken's Avatar
    Join Date
    Jun 2004
    Location
    Copenhagen, Denmark
    Posts
    6,157
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    This is definitely a useful pattern - I use it all the time myself. Normally, I wouldn't bother declaring an interface for something like that, but I guess that's a matter of personal taste. I also prefer to use a callback type as the observer, but again - that's a matter of preference.

    I don't see why you have notify() as part of the interface declaration?

  3. #3
    SitePoint Guru aamonkey's Avatar
    Join Date
    Sep 2004
    Location
    kansas
    Posts
    953
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by kyberfabrikken View Post
    This is definitely a useful pattern - I use it all the time myself.
    I agree--it's beautiful in its simplicity...I've been going through all my old classes and seeing if there are methods that don't belong there...the pattern is really a great tool for handling this.

    Quote Originally Posted by kyberfabrikken View Post
    Normally, I wouldn't bother declaring an interface for something like that, but I guess that's a matter of personal taste... I don't see why you have notify() as part of the interface declaration?
    I probably don't need the interface at all--same goes for the notify() method within it...but I'm trying to use this interface anytime I use the pattern so that I am forced to be consistent.

    Quote Originally Posted by kyberfabrikken View Post
    I also prefer to use a callback type as the observer, but again - that's a matter of preference.
    That sounds interesting...do you have a quick example available?

    Thanks,

    aamonkey

  4. #4
    SitePoint Wizard silver trophy kyberfabrikken's Avatar
    Join Date
    Jun 2004
    Location
    Copenhagen, Denmark
    Posts
    6,157
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by aamonkey View Post
    That sounds interesting...do you have a quick example available?
    PHP Code:
    class Email
    {
        protected 
    $observers = Array();
        function 
    attach($callback) {
            
    $this->observers[] = $callback;
        }
        protected function 
    notify() {
            foreach (
    $this->observers as $callback) {
                
    call_user_func($callback$this);
            }
        }
    }

    // example usage
    $email = new Email('plain');
    $email->attach(Array(new EmailLogger(), 'update'));
    $email->setFrom('me@example.com');
    // snip...more setters
    $email->send(); 

  5. #5
    Non-Member
    Join Date
    Jan 2003
    Posts
    5,748
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    PHP Code:
    // ...
    call_user_func($callback$this); 
    Too procedural for my liking, but again that's a personal point of view; I would on the otherhand, prefer the interface approach


Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •