Observer Design Pattern – Explained and Implemented in PHP

Preamble / Kudos

In the preparation of Episode 9 of my infamous screencast series how to build a PHP Framework using Symfony2 Components I have realized, that I should take a pause and explain a very important design pattern implemented there – the Observer. So I decided to make a screencast about it!
I naturally used the classics – Gang of Four Book, aka Design Patterns: Elements of Reusable Object-Oriented Software. You can take a look at my side notes as I’ve studied it in the section below – The Observer – Gang of Four format, ruined with my comments.
I have also took a liberty at modifying the implementation of the Observer shown by Matt Zandstra in his book PHP Objects, Patterns and Practice.

What we’ll be building

It’s a simple Login class that does what the name suggests – logs the users in and has to send the various information out depending of the result of that action. For instance an E-Mail’s gotta go out when the user forgot the password, or SMS alert should wake the sysadmin to tell him we’re experiencing a break-in attempt.

UML

The Code

Login Class

This is our working horse. We are defining several constants to represent the attempt in the machine-readable format. 1 for unknown user, 2 for wrong pass and 3 for successful log in. The central method is handleLogin() which randomly generates one of these numbers to simulate different scenarios for the test purposes.
The most important part – the Login Class implements the Observable interface, also known as the Subject interface.

<?php
// /classes/Login.php

class Login implements SplSubject
{
    const LOGIN_USER_UNKNOWN = 1;
    const LOGIN_WRONG_PASS = 2;
    const LOGIN_ACCESS = 3;
    private $status = array();
    private $storage;

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

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

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

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

    public function handleLogin($user, $pass, $ip)
    {
        switch (rand(1, 3)) {

            case 1:
                $this->setStatus(self::LOGIN_ACCESS, $user, $ip);
                $ret = true;
                break;
            case 2:
                $this->setStatus(self::LOGIN_WRONG_PASS, $user, $ip);
                $ret = false;
                break;
            case 3:
                $this->setStatus(self::LOGIN_USER_UNKNOWN, $user, $ip);
                $ret = false;
                break;
        }

        $this->notify();

        return $ret;
    }

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

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

LoginObserver Abstract Class

This abstract class implement the Observer interface and takes care of the housekeeping. For instance the Observable instances get initiated and the Observers get attached. Also the Template Method doUpdate is defined, which all of the child classes should implement.

<?php
// /classes/LoginObserver.php

abstract class LoginObserver implements SplObserver
{
    private $login;

    public function __construct(Login $login)
    {
        $this->login = $login;
        $login->attach($this);
    }

    public function update(SplSubject $subject)
    {
        if ($subject === $this->login) {
            $this->doUpdate($subject);
        }
    }

    abstract function doUpdate(Login $login);
}

Concrete Observers – GeneralLogger, EmailNotifier, SMSNotifier

The classes below all extend the abstract LoginObserver class and depending on the status code of the log in attempt do the required action.

<?php
// /class/GeneralLogger.php

require_once 'LoginObserver.php';

class GeneralLogger extends LoginObserver
{
    public function doUpdate(Login $login)
    {
        $status = $login->getStatus();

        if ($status[0] == Login::LOGIN_ACCESS) {
            // Log this in our log files
            print __CLASS__ . "\t logging the successful attempt to log the user in \n";
        }
    }
}
<?php
// /classes/EmailNotifier.php

require_once 'LoginObserver.php';

class EmailNotifier extends LoginObserver
{
    public function doUpdate(Login $login)
    {
        $status = $login->getStatus();

        if ($status[0] == Login::LOGIN_WRONG_PASS) {
            // Log this in our log files
            print __CLASS__ . "\t User has entered a wrong password. Stupid bastard! \n";
        }
    }
}
<?php
// /classes/SMSNotifier.php

require_once 'LoginObserver.php';

class SMSNotifier extends LoginObserver
{
    public function doUpdate(Login $login)
    {
        $status = $login->getStatus();

        if ($status[0] == Login::LOGIN_USER_UNKNOWN) {
            // Log this in our log files
            print __CLASS__ . "\t ALERT! The User name is unknown - Break in attempt! \n";
        }
    }
}

The Client Code

To use the build a system we’re writing a test client script and see our Observers first get attached to the Subject and then send out different notifications depending on the changes in the Subject.

<?php
// test.php

require 'classes/Login.php';
require 'classes/GeneralLogger.php';
require 'classes/EmailNotifier.php';
require 'classes/SMSNotifier.php';

$login = new Login();
new GeneralLogger($login);
new EmailNotifier($login);
new SMSNotifier($login);

$login->handleLogin('andrey', '123456', '192.192.09.00');

The Observer – Gang of Four format, ruined with my comments

Intent

Define a one-to-many dependency between objects so that when one object changes state, all its dependets are notified and updated automatically.

AKA

Dependents, Publish-Subscribe

Motivation

We’ve got a system consisting of a collection of cooperating classes. We need these classes to maintain consistency . In other words, we need one classes to know what the others are doing. But we don’t need to tightly couple these classes together – because we want to reuse the classes separately. How to achive that?

We need the classes that do not know about each other explicitly. But they behave as though they do. Something changes in one class and all the others automatically know the change has happened.

The key objects in this pattern:
– subject
– may have 1 .. n dependant observers
– observers
– are notified whenerver the subject undergoes a change in state
– update themselves accordingly

Another name for that pattern is publish-subscribe. It works just as the name suggests. For example, let’s say we’ve got a newsletter. In the terms of patterns the newsletter is the publisher that sends out the notifications. The users may subscribe (= leave their e-mail addresses) to the newsletter to get the current issue. The letter itself does not have to know who the users are, who have subscribed. Only their e-mail addresses.

Applicability

Use the Observer pattern when:

  • A change in obe object requires changing the others, and you don’t know how many objects need to be changed
  • An object should be able to notify other objects, but without making assuptions about who these obejcts are. In other words, these objects are decoupled. (Like a newsletter is decoupled from me as a subscriber).

Consequences

  • Support for broadcast communication
    • The notification is broadcast automatically to all interesed objects that subscribed to it. Example: Drupal hooks. On it’s way a Drupal request undergoes several stages – when the page is loaded, when the user is loaded etc. Using hooks the modules get their code integrated as a stage in question is reached. Say, hook_node_insert() gets called right at the moment when the Drupal request is inserting a new node. Drupal core has no idea about your module. All it cares about – notify any modules that subscribed to this (= used the hook), that the change is about to happen.
andremaha

About The Author

2 Comments

  1. Alin Dobra says:

    Simple, nice and useful.

    • andremaha says:

      Thank you, Allin! I’m in the process of doing other screencasts on topic of Design Patterns. Any suggestions? Some patterns that would be especially interesting for you?

Leave A Reply