PHP Design Patterns – Observer Pattern

By , Tuesday 29th December 2009 10:02 pm

I’ve been reading Head First Design Patterns recently and have decided to write some of the patterns as PHP examples for my own benefit. The first one that I’ve decided to code up is the Observer Pattern. The formal definition of the Observer Pattern is:

The observer pattern (a subset of the asynchronous publish/subscribe pattern) is a software design pattern in which an object, called the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes, usually by calling one of their methods. It is mainly used to implement distributed event handling systems.

As systems become more loosely coupled making sure that when an event happens all systems that require knowledge of these updates are informed. For example, a blog post, after saving a post we may need to update a search engine (e.g. Lucene), update our sitemap, tags, email subscribed users, etc. The observer pattern allows developers to add additional listeners without editing their observable object. By injecting observers (i.e. a search engine update observer, a sitemap generator, etc) into a subject (i.e. blog post editing system) we can allow the it to perform all the necessary updates without any changes.

Before the Observer pattern was identified the usual trick would be to update the observable object with an additional line of code to update the required system, and removing the line of code as required. This does not allow for easily adding and removing observers.

The subject updates all of its observers via an update method. This update method calls a method in each observer which is derived from implementing an interface. Observers can add an remove themselves through methods in the observable object.

That’s basically it! I always find an example to be the best method of learning/understanding so here’s my coded up example…

Observer Pattern in PHP

In my example I’ve created a news system (NewsAggregator) which sends out news headline updates to smaller news feeds. Here the news sytem takes the place of the Subject, Observable, etc whereas the news feeds take the role of the Observers or Listeners.

Once initialised observers can attach and detach themselves from the subject as they see fit. In my example I have created three observers, these scan the headlines sent out by the subject and ‘shout’ the news if its appropriate. The three observers are named below along with the terms they scan for when receiving news headlines:

  • Sport Observer: ‘rugby’, ‘football’, ‘tennis’
  • News Observer: ‘politics’, ‘finance’, ‘government’
  • Gossip Observer: ‘celebrity’, ‘music’, ‘fashion’

After initalising the subject I add the news and gossip observers and send out a news update. After this the sport observer is added before more news updates are sent out. Lastly the gossip observer is removed before a final news headline is sent out.

The three different observer classes implement the interface Observer, this gives them a clear interface/method through which they will receive updates. Provided they implement the Observer interface they will be able to attach themselves to the Subject. This also keeps with the programming paradigm of ‘program to interfaces not implementations’. The NewsAggregator class extends the abstract class Subject, which provides us with the three required public methods:

  1. updateObservers()
  2. addObserver()
  3. removeObserver()

The code can be seen running here, Observer Pattern in PHP Running, and the code can be downloaded from here, Observer Pattern in PHP Code.

Observer Script

<?php
/**
 * This file contains the observers
 *
 * @author Lloyd Watkin
 * @since 2009/12/23
 */

abstract class Subject
{
	abstract public function addObserver(Observer $observer);
	abstract public function removeObserver(Observer $observer);
	abstract public function updateObservers( $newsHeadline );
}

/**
 * This is the subject class for the example
 *
 * @author Lloyd Watkin
 * @since 2009/12/23
 */
class ArticleAggregator extends Subject
{
	/**
	 * Holds a list of our observers
	 *
	 * @var array
	 */
	protected $_observerList = array();

	/**
	 * Method to add an observer
	 *
	 * @var Observer $observer
	 * @return void
	 */
	public function addObserver(Observer $observer)
	{
		$this->_observerList[] = $observer;
	}

	/**
	 * Method to remove an observer
	 *
	 * @var Observer $observer
	 * @return boolean
	 */
	public function removeObserver(Observer $observer)
	{
		foreach ($this->_observerList AS $key => $ob) {
			if ($ob == $observer) {
				unset($this->_observerList[$key]);
				return true;
			}
		}
		return false;
	}

	/**
	 * Method to update observers
	 *
	 * @var string $newsHeadline
	 * @return void
	 */
	public function updateObservers( $newsHeadline )
	{
		foreach ($this->_observerList AS $ob) {
			$ob->update( $newsHeadline );
		}
	}

	/**
	 * Add a new news story
	 *
	 * @var string $story
	 * @return void
	 */
	public function addNewsStory( $story )
	{
		if ( empty( $story ) || !is_string( $story) ) {
			throw new InvalidArgumentException('Expected a news story!');
		}
		$this->updateObservers( $story );
	}
}

Subject / Observable Script

<?php
/**
 * This file contains the subject
 *
 * @author Lloyd Watkin
 * @since 2009/12/23
 */

abstract class Subject
{
	abstract public function addObserver(Observer $observer);
	abstract public function removeObserver(Observer $observer);
	abstract public function updateObservers( $newsHeadline );
}

/**
 * This is the subject class for the example
 *
 * @author Lloyd Watkin
 * @since 2009/12/23
 */
class ArticleAggregator extends Subject
{
	/**
	 * Holds a list of our observers
	 *
	 * @var array
	 */
	protected $_observerList = array();

	/**
	 * Method to add an observer
	 *
	 * @var Observer $observer
	 * @return void
	 */
	public function addObserver(Observer $observer)
	{
		$this->_observerList[] = $observer;
	}

	/**
	 * Method to remove an observer
	 *
	 * @var Observer $observer
	 * @return boolean
	 */
	public function removeObserver(Observer $observer)
	{
		foreach ($this->_observerList AS $key => $ob) {
			if ($ob == $observer) {
				unset($this->_observerList[$key]);
				return true;
			}
		}
		return false;
	}

	/**
	 * Method to update observers
	 *
	 * @var string $newsHeadline
	 * @return void
	 */
	public function updateObservers( $newsHeadline )
	{
		foreach ($this->_observerList AS $ob) {
			$ob->update( $newsHeadline );
		}
	}

	/**
	 * Add a new news story
	 *
	 * @var string $story
	 * @return void
	 */
	public function addNewsStory( $story )
	{
		if ( empty( $story ) || !is_string( $story) ) {
			throw new InvalidArgumentException('Expected a news story!');
		}
		$this->updateObservers( $story );
	}
}

Controller Script

<?php
/**
 * Observer Design Pattern Example
 *
 * @author Lloyd Watkin
 * @since 2009/12/23
 * @link http://www.evilprofessor.co.uk
 */
include 'observers.php';
include 'subject.php';

if (!empty($_SERVER['HTTP_USER_AGENT'])) {
    echo '
';
}

// What are we doing?
echo 'Observer Pattern Example in PHP' . PHP_EOL;
echo '================================' . PHP_EOL;
// Set up our subject
$subject = new ArticleAggregator();
echo ' - ArticleAggregator created' . PHP_EOL;

// Add some observers
$subject->addObserver( new NewsObserver() );
$subject->addObserver( $gossiper = new GossipObserver() );

echo ' - Added NewsObverser & GossipObserver' .
	 PHP_EOL . PHP_EOL;

// Beep, beep, beep... News Flash!
echo 'NewsFlash: celebrity rugby player loves finance' . PHP_EOL;
echo '================================================' . PHP_EOL;
$subject->addNewsStory('celebrity rugby player loves finance');
echo PHP_EOL;

echo ' - SportObserver has found out and wants to join the group!';
$subject->addObserver( new SportObserver() );
echo PHP_EOL . PHP_EOL;

// Beep, beep, beep... News Flash!
echo 'NewsFlash: government messes up again!' . PHP_EOL;
echo '=======================================' . PHP_EOL;
$subject->addNewsStory('government messes up again!');
echo PHP_EOL;

// Beep, beep, beep... News Flash!
echo 'NewsFlash: fashion and football combine' . PHP_EOL;
echo '=======================================' . PHP_EOL;
$subject->addNewsStory('fashion and football combine');
echo PHP_EOL;

// Beep, beep, beep... News Flash!
echo 'NewsFlash: music and politics, what next?' . PHP_EOL;
echo '==========================================' . PHP_EOL;
$subject->addNewsStory('music and politics, what next?');
echo PHP_EOL;

/**
 * Gossipers grow tired of news very quickly and have decided
 * to stop listening, despite all the interesting news today!
 */
echo ' - GossipObserver is bored and leaves the group!' .
     PHP_EOL . PHP_EOL;
$subject->removeObserver( $gossiper );

// Beep, beep, beep... News Flash - Update to an earlier story!
echo 'NewsUpdate: fashion and football combine says ' .
     'government' . PHP_EOL;
echo '================================================' .
     '=========' . PHP_EOL;
$subject->addNewsStory( 'fashion and football combine ' .
                        'says government' );
echo PHP_EOL;

if (!empty($_SERVER['HTTP_USER_AGENT'])) {
    echo '

';

}

3 Responses to “PHP Design Patterns – Observer Pattern”

  1. Vow says:

    Hello,
    First,My english is very poor,then
    Can you tell me what is the system in the picture?
    fedora 12?
    What is the theme?

  2. Vow says:

    Hello,
    Can you tell me what is the system in the picture?
    Fedora 12?
    What is the theme?

Panorama Theme by Themocracy

2 visitors online now
0 guests, 2 bots, 0 members
Max visitors today: 4 at 02:14 am UTC
This month: 12 at 15-09-2014 02:03 pm UTC
This year: 79 at 09-06-2014 07:22 pm UTC
All time: 130 at 28-03-2011 10:40 pm UTC