Implementing the Observer Pattern with SplObserver and SplSubject

June 9, 2010

Uncategorized

As PHP applications grow into complex object-oriented systems, developers look to create centralized components to execute repetitive tasks. These include logging, emailing, redirects, and more. The Observer pattern is a commonly used design pattern to hook such components into an application during runtime, thereby making them reusable. Since PHP 5.1, there are two interfaces built into the Standard PHP Library (SPL) that can be implemented to use the Observer pattern in your application. They are SplSubject, and SplObserver.

As a sample implementation, we are going to create an uncaught exception handler class, aptly called ExceptionHandler. This class will implement the SplSubject interface. To implement SplSubject, three methods must be implemented: attach(), detach(), and notify(). Before showing the implementation, let’s go over what each method is supposed to do:

  • attach() – Accepts an observer that is then stored in the object
  • detach() – Removes an existing observer from the object
  • notify() – Notifies all attached observers

The notify() method definition in particular is vague, because notification depends on the context. In our case, we want to notify our observers when there in an uncaught Exception. With that explained, let’s take a look at the ExceptionHandler class:

/**
* The ExceptionHandler class sends uncaught
* exception messages to the proper handlers.  This is done
* using the Observer pattern, and SplObserver/SplSubject.
*/
class ExceptionHandler implements SplSubject
{
    /**
    * An array of SplObserver objects
    * to notify of Exceptions.
    *
    * @var array
    */
    private $observers = array();

    /**
    * The uncaught Exception that needs
    * to be handled.
    *
    * @var Exception
    */
    public $exception;

    /**
    * Constructor method for ExceptionHandler.
    *
    * @return ExceptionHandler
    */
    function __construct() { }

    /**
    * Attaches an SplObserver to
    * the ExceptionHandler to be notified
    * when an uncaught Exception is thrown.
    *
    * @param SplObserver        The observer to attach
    * @return void
    */
    public function attach(SplObserver $obs)
    {
        $id = spl_object_hash($obs);
        $this->observers[$id] = $obs;
    }

    /**
    * Detaches the SplObserver from the
    * ExceptionHandler, so it will no longer
    * be notified when an uncaught Exception is thrown.
    *
    * @param SplObserver        The observer to detach
    * @return void
    */
    public function detach(SplObserver $obs)
    {
        $id = spl_object_hash($obs);
        unset($this->observers[$id]);
    }

    /**
    * Notify all observers of the uncaught Exception
    * so they can handle it as needed.
    *
    * @return void
    */
    public function notify()
    {
        foreach($this->observers as $obs)
        {
            $obs->update($this);
        }
    }

    /**
    * This is the method that should be set as the
    * default Exception handler by the calling code.
    *
    * @return void
    */
    public function handle(Exception $e)
    {
        $this->exception = $e;
        $this->notify();
    }
}

Notice that there is an extra method defined in ExceptionHandler – the handle() method. This is the method that we are going to make the default for uncaught Exceptions (see last code snippet). Notice that this method stores the uncaught Exception as a member variable, and then calls notify. We do this so that the observers can each have access to the Exception, while maintaining the SplSubject interface!

The SplObserver should be implemented as your component – in our example, we have a Logger class and a Mailer class. SplObserver is much easier to implement; we have only one method, called update(), which takes the calling SplSubject as its only parameter. Any logging, emailing, etc. should either be executed or called from within this method. Here is a simple Logger implementation:

/**
* The Logger class is responsible for logging uncaught
* exceptions to a file for debugging.
*/
class Logger implements SplObserver
{
    /**
    * Update the error_log with
    * information about the Exception.
    *
    * @param SplSubject $subject   The ExceptionHandler
    * @return boolean
    */
    public function update(SplSubject $subject)
    {
        return error_log($subject->exception);
    }
}

And also a Mailer:

/**
* The Mailer class is responsible for mailing uncaught
* exceptions to an administrator for notifications.
*/
class Mailer implements SplObserver
{
    /**
    * Mail the sysadmin with Exception information.
    *
    * @param SplSubject $subject   The ExceptionHandler
    * @return boolean
    */
    public function update(SplSubject $subject)
    {
        // Set up mail here
        // ...
    }
}

Now all we have left to do is create the code and throw an Exception. The following code should go in some sort of application init() method, so it is created and set once upon the start of the request cycle:

/**
* This is the code to set up the ExceptionHandler
* and attach necessary observers.
*/
require_once('ExceptionHandler.php');
require_once('Logger.php');
require_once('Mailer.php');

// Create the ExceptionHandler
$handler = new ExceptionHandler();

// Attach an Exception Logger and Mailer
$handler->attach(new Logger());
$handler->attach(new Mailer());

// Set ExceptionHandler::handle() as the default
set_exception_handler(array($handler, 'handle'));

Now, at any point during the application, any throw new Exception(‘Place your error message here’) that is uncaught will be both logged and emailed to your sysadmin. Pretty handy!

Hopefully this blog will get you started in your process of creating good modular components for your application, while using the Observer pattern to fit the components into a larger system. There are many uses for this pattern. In the next blog, I am going to outline how we can use the Observer pattern to create an event-driven component system, complete with custom event handlers. Should be a good challenge!

2 Responses to “Implementing the Observer Pattern with SplObserver and SplSubject”

  1. julienanest Says:

    Thanks for the article.
    It’s not the point of this article, but using SplObjectStorage in this case could also be profitable as ExceptionHandler’s $observers could be an object this kind instead of being an array.
    In fact, I wonder why SplSubject is not a class extending SplObjectStorage instead of being an interface.

  2. jcook21 Says:

    Thanks so much for this article. I really enjoyed it. I also thought that this would be a great way to track non-fatal errors in an application such as E_STRICT or E_WARNING. I often find that these kinds of errors can cause subtle bugs in an application but are hard to find unless you look in the log files. Using the observer pattern in a way like you’ve outlined above would be a great way of notifying developers if these kind if errors occur.