PHP - - By Ignatius Teo

Understanding the Observer Pattern

Recently I was asked to integrate third-party forum software into an existing web portal. The problem was, both applications had their own separate authentication mechanism. From a user perspective it would be ideal for users to log in to the portal without having to log in to the forum separately. And in this case, I didn’t want to modify the forum code unnecessarily since it would create a maintenance nightmare – I’d have to merge any subsequent updates and bug fixes from the vendor and be careful not to overwrite my own modifications. This is where the Observer Pattern came in handy for me.

In this article I’ll show you how to implement the Observer Pattern. You’ll learn how various classes in the pattern relate to one another as subject and observers, how the subject notifies observers of a change in its state, and how to identify scenarios where it would be suitable to use the Observer Pattern in your own code.

The Observer Pattern

The Observer Pattern (also known as Publish-Subscribe Pattern) is a behavioral design pattern which defines a one-to-many relationship between objects such that, when one object changes its state, all dependent objects are notified and updated automatically. In my case, I used this pattern to link the portal’s authentication mechanism with the forum software. The act of logging into the portal also automatically triggered the user to be logged into the forum as well.

An object with a one-to-many relationship with other objects who are interested in its state is called the subject or publisher. Its dependent objects are called observers or subscribers. The observers are notified whenever the state of the subject changes and can act accordingly. The subject can have any number of dependent observers which it notifies, and any number of observers can subscribe to the subject to receive such notifications.

The Observer Class

The Observer class provides an update() method which will be called by the subject to notify it of the subject’s state change. In this example, I’ve defined the update() method as a concrete method. If you like, you can define the method here as abstract and then provide a concrete implementation for it in your observer’s subclass.

<?php
abstract class Observer
{
    public function __construct($subject = null) {
        if (is_object($subject) && $subject instanceof Subject) {
            $subject->attach($this);
        }
    }

    public function update($subject) {
        // looks for an observer method with the state name
        if (method_exists($this, $subject->getState())) {
            call_user_func_array(array($this, $subject->getState()), array($subject));
        }
    }
}

The __construct() method accepts an instance of an observable subject and attaches itself to the subject – which I’ll get to momentarily. The update() method retrieves the subject’s current state and uses it to call a subclassed observer method with the same name as the state.

So, in my particular case, I needed to make the portal’s existing Auth class an observable subject and create a concrete observer subclass to hook into the forum’s authentication code. My subclass also needed to implement methods using the subject’s states.

The Subject Class

The Subject class is also an abstract class and defines four primary methods: attach(), detach(), setState(), and notify(). For convenience, I’ve also added getState() and getObservers() methods here.

<?php
abstract class Subject
{
    protected $observers;
    protected $state;

    public function __construct() {
        $this->observers = array();
        $this->state = null;
    }

    public function attach(Observer $observer) {
        $i = array_search($observer, $this->observers);
        if ($i === false) {
            $this->observers[] = $observer;
        }
    }

    public function detach(Observer $observer) {
        if (!empty($this->observers)) {
            $i = array_search($observer, $this->observers);
            if ($i !== false) {
                unset($this->observers[$i]);
            }
        }
    }

    public function getState() {
        return $this->state;
    }

    public function setState($state) {
        $this->state = $state;
        $this->notify();
    }

    public function notify() {
        if (!empty($this->observers)) {
            foreach ($this->observers as $observer) {
                $observer->update($this);
            }
        }
    }


    public function getObservers() {
        return $this->observers;
    }
}

The attach() method subscribes an observer to the subject so that any changes in state can be conveyed to it. The detach() method unsubscribes the observer from the subject, so that it no longer observes the subject for state changes.

The setState() method sets the subject’s current state and calls notify() to update the observers, i.e. publishes notifications to each observer. The notify() method in turn updates each of the subscribed objects by iterating through the internal list and calling each member’s update() method.

The getState() and getObservers() methods are simply helper functions to return the current subject’s state and list of observers.

Observe Closely… Bringing it Together

Now with abstract base classes for both an observer and subject, I was able to integrate the forum software into the existing web portal. I needed to make the portal’s Auth class an observable subject and set its observable state when the user logs in or out of the portal.

<?php
class Auth extends Subject
{
    function login() {
        // existing code to perform login authentication
        // ...

        // signal any observers that the user has logged in
        $this->setState("login");
    }

    function logout() {
        // existing code to perform some operation when user logs out
        // e.g. destroy session, etc...

        // signal any observers that the user has logged out
        $this->setState("logout");
    }
}

I extended the Subject class so that Auth is observable, and then added a call to setState() in both the login() and logout() methods.

To subclass the Observer, I created an Auth_ForumHook class which was responsible for calling the forum’s API login and logout functions.

<?php
class Auth_ForumHook extends Observer
{
    function login($subject) {
        // call the forum's API functions to log the user in
        // ...
    }

    function logout($subject) {
        // call the forum's API functions to log the user out
        // ...
    }
}

Elsewhere in the code base, where the portal’s Auth class was being instantiated, I attached an Auth_ForumHook, instance to it so that the observer would be notified of any state changes in Auth.

<?php
$auth = new Auth();
// attach an observer
$auth->attach(new Auth_ForumHook());
...

That was the extent of my extra coding requirements – apart from preparing the abstract Observer and Subject classes. Any change in state triggered by Auth‘s login() and logout() methods notified the Auth_ForumHook observer and automatically logged the user in or out of the forum.

To add new observers, say, a login tracker to record when a user logs in or out of the portal, one would just have to provide a concrete Observer class and attach it to the Auth subject without the need to further modify the existing Auth object’s login() and logout() methods.

Summary

The Observer Pattern is an appropriate design pattern to apply in any situation where you have several objects which are dependent on another object and are required to perform an action when the state of that object changes, or an object needs to notify others without knowing who they are or how many there are.

In this article, I have shown you the basic Subject-Observer pattern and provided concrete examples of how you can use this behavioral pattern to easily extend the functionality of an existing class without tightly coupling any new requirements. This pattern enables you to achieve a higher level of consistency between related and dependent objects without sacrificing code re-usability.

Image via JPF / Shutterstock

Sponsors
Login or Create Account to Comment
Login Create Account