PHP Patterns: The Observer Pattern

The Observer Pattern is probably one of my favourite patterns.

It’s fairly straightforward, flexible, and best of all, the base classes you need to implement the Observer Pattern are available in the Standard PHP Library, so it should be available to all PHP5 applications.

Let’s jump straight in, and take a look at a possible scenario where we could use this pattern.

A Login System

Typically, the Observer Pattern works best when we need some sort of event-handling system where an object, known as the Subject, keeps track of a list of other objects, called Observers, which need to be notified when some change occurs within the system.

An ideal example would be something like a Login system.

Let’s say you have an object, like a Login class, that would be responsible for handling the core functionality of signing someone into a system. In the Observer Pattern, we would make this the Subject.

However, there may be a number of other processes that should be notified based on what happens in the Login class.

Perhaps you’ve got some sort of Logging system which is there to log any messages related to the login process. This would note things like the date and time the user attempted to log into the system, their IP address, whether they were successful, any error messages, and the like.

Likewise, you may want to implement some sort of Security system as well: if the user logged in with an incorrect password, perhaps you’d want to send an email out to an administrator, or log that into a separate log file somewhere. Or perhaps you keep track of the number of times a user has tried to login, and if they try more than three times, you want to lock down that account and email the Administrator.

You could have several other objects as well, all of which need to be notified when something takes place in relation to our Login class.

All of these are what we would call our Observers.

To make matters more complicated, let’s aim for some flexibility, and decide that we also don’t want to force all these Observers to always be used when we perform logins. We want the ability to attach or remove Observers as we see fit: perhaps one system only needs a Security feature, and another only needs Logging. Perhaps we don’t need any of those, and want to use something else.

Sound impossible? Not with the Observer Pattern.

The Standard PHP Library: SplObserver, SplSubject, and SplObjectStorage

Before we continue, let’s take a look at three classes that the Standard PHP Library (SPL) provides for using the Observer Pattern.

As you no doubt guessed, SplSubject and SplObserver are exactly what we’ve been talking about: our base Login class would be the SplSubject, while our Logging and Security classes would be our SplObservers.

We’ve not mentioned anything about SplObjectStorage as yet, but we’ll get there.

Let’s take a look at SplSubject first.

SplSubject

If we were to write this class ourselves, here is an example of how it would look.

Remember: we don’t need to write these classes into our code as they’re already built; I simply want to show you what they look like.

interface SplSubject {
	public function attach( SplObserver $observer );
	public function detach( SplObserver $observer );
	public function notify();
}

You’ll notice it’s simply an interface with three methods defined: attach, which is for attaching SplObserver objects that we need to notify; detach, which is for removing any SplObserver objects we no longer want; and notify, which is for – you guessed it – notifying all the attached SplObserver objects that a change has occurred.

The actual functionality for these methods are left up to you to implement, but we’ll see how we do that later.

SplObserver

Here is how the SplObserver interface looks like:

interface SplObserver {
	public function update( SplSubject $subject );
}

This is a very simple interface, with only one required method, update, which you can consider as a type of initialisation method. Whenever our Observers are notified of a status change, this is the function that will be called.

SplObjectStorage

But what about SplObjectStorage?

As the name suggests, this is just an easy storage system for our Observers. Since we want the ability of attaching or detaching objects to and from our system, we’ll need some sort of storage system to keep track of what Observer objects we need to notify.

While I won’t reproduce the full class here (you can look at all the methods available at PHP.net), the two we are most interested in are attach and detach, which allow us to add and remove objects to and from our storage container. We’ll see how in a second, as we now look at putting it all together.

Putting it Together

Login Class

Now comes, the fun stuff. Let’s construct a basic Login class. For brevity, I’ll not write the actual Login functionality, and will just randomly set a status code.

class Login implements SplSubject {

	const UNKNOWN_USER = 1;
	const INCORRECT_PWD = 2;
	const ALREADY_LOGGED_IN = 3;
	const ALLOW = 4;

	private $status = array();
	private $storage;

	function __construct() {
		$this->storage = new SplObjectStorage();
	}

	function init( $username, $password, $ip ) {

		// Let's simulate different login procedures
		$this->setStatus( rand( 1, 4 ), $username, $ip);

		// Notify all the observers of a change
		$this->notify();

		if ( $this->status[0] == self::ALLOW ) {
			return true;
		}

		return false;

	}

	private function setStatus( $status, $username, $ip ) {
		$this->status = array( $status, $username, $ip );
	}

	function getStatus() {
		return $this->status;
	}

	function attach( SplObserver $observer ) {
		$this->storage->attach( $observer );
	}

	function detach( SplObserver $observer ) {
		$this->storage->detach( $observer );
	}

	function notify() {

		foreach ( $this->storage as $observer ) {
			$observer->update( $this );
		}

	}

}

Let’s take a look at what’s happening.

First, you’ll notice in the __construct() that we immediately initialise our SplObjectStorage to store our Observers.

Further on in the code, you’ll see the attach() and detatch() methods. As you can see, they simply accept an SplObserver object as an argument, and then either attach or detach them from our storage object.

setStatus() stores an array consisting of the status code, username, and IP address for the current Login attempt, and getStatus() allows us to retrieve that array.

Finally, before we get to the main bulk of the class, take a look at notify(). As mentioned before, this will enable us to notify all the Observers we attached to our storage object that something happened. As you can see, all we do is loop over all the Observers in storage, and call their update() method, passing our Subject object as an argument.

The method that brings them all together is init(). Here you’ll see that I’m setting a random status to simulate various types of logins, and I’m then calling update() in order to notify all my Observers that something has happened.

Our Observers: Security and Logging Classes

Let’s go ahead and design some skeleton classes that simulate our Security and Logging requirements.

Security
class Security implements SplObserver {

	function update( SplSubject $SplSubject ) {

		$status = $SplSubject->getStatus();

		switch ( $status[0] ) {

			case Login::INCORRECT_PWD:
				echo __CLASS__ . ": Incorrect password. Storing attempt, and emailing admin on third attempt.";
				break;

			case Login::UNKNOWN_USER:
				echo __CLASS__ . ": Unknown user. Storing attempt, and block IP on tenth try.";
				break;

			case Login::ALREADY_LOGGED_IN:
				echo __CLASS__ . ": User is already logged in, check to see if IP addresses are the same.";
				break;

		}

	}

}
Logging
class Logging implements SplObserver {

	function update( SplSubject $SplSubject ) {

		$status = $SplSubject->getStatus();

		switch ( $status[0] ) {

			case Login::INCORRECT_PWD:
				echo __CLASS__ . ": Logging incorrect password attempt to error file.";
				break;

			case Login::UNKNOWN_USER:
				echo __CLASS__ . ": Logging unknown user attempt to error file.";
				break;

			case Login::ALREADY_LOGGED_IN:
				echo __CLASS__ . ": Logging already logged in to error file.";
				break;

			case Login::ALLOW:
				echo __CLASS__ . ": Logging to access file.";

		}

	}

}

These are very straightforward. Both of them have an update() method, which accepts an object of type SplSubject as an argument.

Then, based on the status code set in our Subject, various actions are performed. At the moment, all I’m doing is echoing out some strings, but it’s easy to see how we could add loads of useful functionality based on what happened in our Subject.

Enough Already, Let’s See it in Action!

So now that we have the basics, how do we use it? Simple!

$login = new Login();
$login->attach( new Security() );
$login->attach( new Logging() );

if ( $login->init( "craigsefton", "password", "127.0.0.1" ) ) {
	echo "User logged in!";
} else {
	echo "<pre>";
	print_r( $login->getStatus() );
	echo "</pre>";
}

All I’m doing here is instantiating a new Login class, attaching my two Observers, Security() and Logging() and then calling init().

As you can already see, it becomes very, very simple to add new Observers. All you need to do is make a new class that extends SplObserver, and then you can use attach() to add them to your Subject.

I could also only add a Logging object if I wanted, and ignore Security. You can also not bother adding any Observers at all if you want, and just log users into the system.

Overall, very cool!

This entry was posted in PHP, Programming and tagged , , , , .

5 Responses to “PHP Patterns: The Observer Pattern”

  1. Senor Castro says:

    Thank you so much for the very informative article. Very inspiring!

  2. Dale says:

    Really nice article, thanks very much. I’m just starting to get into patterns and trying to up my game, I find real world examples much easier to understand, but much harder to find; this was very helpful.

  3. Waschman says:

    Very nice article indeed! I have found so many articles about the observer pattern but they are so generic and common. During my search I found a real world example that I saved somewhere but I couldn’t find it again, so my next search brought me this website.

    Thank you! Worked like a charm.

    I read in the previous article that I lost, that the observer pattern is also good to use for example when you want to integrate third party software code, example: to use a CMS and later you add an external forum. If you want to be automatically logged in to the forum when you are logged in to the CMS, you can hook into the authentication code from the forum. The forum is the observer and the CMS login system is the subject. The CMS notifies the forum observer about a login event and actions are taken.

    Have a good day!

Leave a Reply