PHP Observer Design Pattern: The Basics

observeThe Observer design pattern is one of those patterns that is easy to understand in terms of practical utility, and it’s pretty easy to implement. The idea behind the Observer pattern is to have one main object with other objects depending on the main one to notify them when changes occur. For PHP, the Observer utility is so fundamental, there’s a built-in SplObserver interface. (Using the SplObserver along with the SplSubject will be covered in a future post.)

For example, suppose you have a data source such as an inventory saved in a MySql database. You check on the database periodically to see when you need to purchase more inventory of different products. To make it practical, you want a graph that you can tell at a glance what items need replacement and which ones don’t. However, you also want a table to give you a precise breakdown of the exact numbers in inventory. This example may sound familiar because it’s similar to the one that the Gang of Four use in Design Patterns: Elements of Reusable Object-Oriented Software in illustrating the Observer pattern. What a lot of people don’t realize is that they use the same example at the beginning of their book (page 5) to examine the Model View Controller (MVC). For those of you still using the MVC, the data is supplied by the model, and the views are the displays in different formats (a table, a bar chart, and a pie chart). To get started Play the two examples and Download the source code:
Play
playMul
Download

The Observer Class Diagram

The Observer’s class diagram is a bit perplexing. Take a look at Figure 1, and you’ll see why:

Figure 1: Observer Class Diagram

Figure 1: Observer Class Diagram

The diagram itself is pretty simple, two interfaces (Subject and Observer) and two concrete implementations (ConcreteSubject and ConcreteObserver). What’s a little confusing is the Subject interface: all three methods indicate a concrete implementation because they are not italicized. Italicized method and class (interface) names indicate abstract classes and methods or interfaces. In PHP, the SplSubject is an interface (all methods are abstract), but the diagram clearly shows some concrete implementation in the notify() method—including the pseudo code for it. Neither the attach() nor the detach() are italicized, but the book (p. 295) indicates that the Subject provide attach/detach interfaces—abstract methods. In order to meet the intent of the Subject interface, I created an abstract class that has abstract methods for attaching and detaching Observer objects. However, I also included a concrete notify() method along the lines suggested.

<?php
abstract class Subject
{
    //Abstract attach/detach methods
    public abstract function attach(Observer $observer);
    public abstract function detach(Observer $observer);
 
    //Concrete protected arrays and  public method
    protected $observerSet=array();
    protected $dataSet=array();
    public function notify()
    {
        foreach($this->observerSet as $value)
        {
            $value->update($this);
        }
    }
}
?>

The idea of the Observer is to have a concrete Subject class (or classes), and have Observer objects subscribe to the data in the Subject. It’s something like a News Service used by Television, Radio, Newspapers and News Blogs. The News Service is the Subject and the news outlets are subscribers — concrete Observers. Notice in Figure 1 that the arrow from the Subject to the Observer ends with a solid ball. That means that the relationship is one (Subject) to many (Observers.) In the example provided, you can add data to a hypothetical “inventory” program that then forwards it to a Subject, and the Subject makes it available to different Observers. In this case there are only two: one that uses the Subject information to create a bar chart, and another that uses the data to display a data table.

Next, look at the ConcreteSubject named, HardwareSubject.

<?php
class HardwareSubject extends Subject
{
    public function attach(Observer $observer)
    {
        array_push($this->observerSet,$observer);
    }
 
    public function detach(Observer $observer)
    {    
        $counter=0;
        foreach($this->observerSet as $value)
        {
            if($this->observerSet[$counter]==$observer)
            {
                unset($this->observerSet[$counter]);
            }
            $counter++;
        }
    }
 
    public function getState()
    {
        return $this->dataSet;
    }
    public function setState($data)
    {
        $this->dataSet=$data;
    }
}
?>

As an extension of the Subject abstract class, it must implement the attach/detach (think subscribe/unsubscribe) methods. The inherited notify() method is concrete; so it’s available for use with no modifications—not seen in the class. The getter/setter methods are used to make the data to the attached Observers (subscribers)— getState() and the setState($data) methods receives the data to be made available to subscribers. In this example, the data are made available through a UI directly, but the data could come from any source—especially a MySQL database. As you will see, the data for the dataSet array (inherited from Subject) gets it data from the array storing the data provided by the Client.

The Observers

The real work horses for this pattern are the concrete Observer objects. The concrete subject takes the data and tosses it out to the subscribed (attached) observers and they’re the ones who really do something with the data from the Subject. Take a look at the Observer interface:

<?php
interface Observer
{
    function update(Subject $subState);
}
?>

As you can see, it’s quite simple with a single method that expects a Subject object as an argument. However, this part can be a bit tricky since the notify() method of the Subject interface includes an update call that uses the current concrete subject as a parameter. That is, the update($sub) is called from a Subject object and includes itself in the form of a $this statement in the parameter.

The Bar Chart
The concrete Observers can be as simple or complex as you want. I went for medium complex. First, take a look at the BarChartObserver. It takes the Subject’s data and makes bar charts using SVG graphics.

<?php
class BarChartObserver implements Observer
{
    private $obState=array();
    private $barChart;
    private $bar;
    private $color;
    public function update(Subject $subState)
    {
        $this->obState=$subState->getState();
        $this->makeChart();
    }
 
    private function makeChart()
    {
        $this->color=array('#0D3257','#97A7B7','#B2C2B9','#BDD6E0','#65754D','#C7DBA9');
        $spacer=0;
        $maxVal=max($this->obState);
        $mf = function($x){return 220/$x;};
        foreach($this->obState as $value)
        {
            $adjSize=$mf($maxVal) * $value;
            $this->buildChart($spacer,$adjSize);
            $spacer+=36.6;
        }
      }
 
      private function buildChart($pos,$barSize)
      {
        $cc= array_pop($this->color);
        $base = 220-$barSize;
        $SVG ="<svg width='220' height='220' viewBox='0 0 220 220'>";
        $this->bar ="<rect x=$pos y=$base width='36.6' height=$barSize fill=$cc stroke='#888' stroke-width='1' />";
        $this->barChart=$SVG . $this->bar;
        echo $this->barChart;
      }
}
?>

Whenever making charts, you have to take in several considerations. I’ll touch on two here. First, the values have to be relative to the largest element in your numerical array. This can be used to set up relative sizes by using the max() method with your array, and the little lambda function stored in $mf. When iterating through the array with a foreach() loop each value is set to the size of the window (220). By using the lambda function with the maximum value as a parameter, the maximum value divided is by 220 is the factor, and that value is multiplied by the factor times the actual value of the data point. That gives you values that will fit in the 220 pixel window relative to the size of the maximum value. (If you have a spiked maximum value, the other value tend to flatten out on the chart.)

The Data Table

In order to get a better picture of this implementation of the Observer design pattern, Figure 2 shows the files used. A PHP helper class and and an additional CSS file aided in the TableObserver class. Figure 2 also helps to put the other files in context:

Figure 2: Observer and Helper Files

Figure 2: Observer and Helper Files

The TableObserver takes the same Subject data and instead of creating a bar chart, it creates a data table; but the data are identical to that of the bar chart. Unlike the bar chart, the data spread can be as much as you want because only the actual integers are displayed—not graphic representations. The rest is a matter of putting the data into tables, and so a second CSS file is employed to address issue specific to the table under construction.

<?php
class TableObserver implements Observer
{
    private $type=array('Mac&nbsp;&nbsp;','HP&nbsp;&nbsp;&nbsp;&nbsp;','RasPi');
    private $obState=array();
    private $barChart;
    private $bar;
    private $color;
    public function update(Subject $subState)
    {
        $this->obState=$subState->getState();
        $this->makeChart();
    }
 
    private function makeChart()
    {
        //create numeric array
        $load=array();
        foreach($this->obState as $value)
        { 
            array_push($load, $value);
        }
 
        //get table top from helper class
        echo MettreLaTable::surLaTable();
        $rn=1;
        $c=count($load)-1;
        for($n=0; $n <= $c; $n+=2)
        {
            $ty=array_pop($this->type);
            $lc=$load[$n];
            $rc = $load[$n+1];
            $this->buildTable($rn,$lc,$rc,$ty);
            $rn++;
        }
        echo"</table></body></html>";
      }
 
      private function buildTable($rn,$lc,$rc,$ty)
      { 
        $alpha= "<tr><th>$ty :</th><td>$lc</td><td>$rc</td></tr>";
        $beta= "<tr class='lg'><th>$ty :</th><td>$lc</td><td>$rc</td></tr>";
        $b=$rn % 2;
        $rowNow=($b==0) ? $alpha : $beta;
        echo $rowNow;
      }
}
?>

The only implementation required is the update() method. Essentially, it gets data from the Subject. It gets the data and then converts the associative array created in the Client and turns it into a numeric array by the simple technique of taking the values out of the associative array and putting them into a numerical one (named $load.) Then, using the numeric keys, it skips through the array and places the values into row pairs for the table.

The reason I chose to use a helper class in this case was because I had to build the “top” of a table. It would be a distraction from the main responsibility of the TableObserver’s role; so I just used the heredoc string syntax, made the table and returned it to the TableObserver. (Mettre La Table is “set the table” in French, and sur la table is “on the table”—I don’t want anyone to get bored.)

<?php
//Helper class
class MettreLaTable
{
    private static $t="";
    public static function surLaTable()
    {
        //Use heredoc syntax
        self::$t=<<<LATABLE
        <!DOCTYPE html><html>
            <head><link rel='stylesheet' type='text/css' href='table.css'>
            </head>
            <body>
                <table>
                    <caption><h4>Computers</h4></caption>
                    <tr><td>&nbsp;</td><th>Ty A</th><th>Ty B</th></tr>
LATABLE;
        return self::$t;
    }  
}
?>

With the top of the table complete, the rest of the table is created by iterating through the array and placing the data pairs in rows—in the TableObserver.

The HTML UI and the Client

The HTML UI simple was a way to generate data that could be used by the program and launch one of the two concrete Observer objects. By using the concrete Observer names as values in the radio buttons, it was easy to select the bar charts or the data table observers. The data was simply integers entered by the user.

<!DOCTYPE html>
<html>
<head>
    <link rel="stylesheet" type="text/css" href="observer.css">
    <title>Observer Chart-Maker</title>
</head>
 
<body>
<h3>Gizmo's Hardware Inventory Tracker</h3>
<form action="Client.php" method="post" target="feedback">
    <input type="text" name="rpi" size="4">&nbsp;Raspberry Pi B+<br />
    <input type="text" name="rpi2" size="4">&nbsp;Raspberry Pi 2<br />
    <input type="text" name="hpdesk" size="4">&nbsp;HP Envy Recline<br />
    <input type="text" name="hplap" size="4">&nbsp;HP Envy Laptop 17<br />
    <input type="text" name="macdesk" size="4">&nbsp;iMac<br />
    <input type="text" name="maclap" size="4">&nbsp;MacBook
    <p>
    <input type="radio" name="observer" value="BarChartObserver" checked="true">&nbsp;Bar Chart&nbsp;
    <input type="radio" name="observer" value="TableObserver">&nbsp;Table
    </p>
    <p>
    <input type="submit" name="sender" value="Send Data"></p>
</form>
<iframe width = 236 height=236 name="feedback">feedback</iframe>
</body>
</html>

The Client, places the form data into an associative array and sends it along to the Subject. It also subscribes (using the attach() method) one of the two Observers. Then it notifies the Subject that an update has occurred, and the updated data are passed to the subscribing Observers.

<?php
error_reporting(E_ALL | E_STRICT);
ini_set("display_errors", 1);
// Autoload given function name.
function includeAll($className)
{
    include_once($className . '.php');
}
//Register
spl_autoload_register('includeAll');
 
class Client
{
    //Encapsulate properties
    private $dataNet=array();
    private $observer;
 
    //Operations
    public function request()
    {
        $this->loadData();
        $subject=new HardwareSubject();
        $subject->attach(new $this->observer);
        $subject->setState($this->dataNet);
        $subject->notify();
    }
 
    private function loadData()
    {
        $this->dataNet['rpi']=intval($_POST['rpi']);
        $this->dataNet['rpi2']=intval($_POST['rpi2']);
        $this->dataNet['hpdesk']=intval($_POST['hpdesk']);
        $this->dataNet['hplap']=intval($_POST['hplap']);
        $this->dataNet['macdesk']=intval($_POST['macdesk']);
        $this->dataNet['maclap']=intval($_POST['maclap']);
        $this->observer = $_POST['observer'];
    }
}
$worker=new Client();
$worker->request();
?>

Unlike some design patterns, the Client is neither included nor implied in the Observer design pattern. On page 297 of their book, the Gang of Four indicate that one way to initiate the notify() method is to have the client do it. That’s what this Observer implementation has done; hence clarity in the role of the Client!

Easy Structure

After going through a rather lengthy post, it’s important to remember that the basic structure of the Observer is quite simple, and for those of you familiar with the MVC, it’s simply a more detailed version of the MVC. At the root, the Observer only has four parts: The Subject and Observer interfaces and the ConcreteSubject and ConcreteObserver. As illustrated in this post, you have more than one—as many as you want. For having multiple observers simultaneously (seen when you click the Play Multiple button) the Client’s attachment statements were changed slightly to attach both of the concrete observers at once. The iframe was adjusted to a width of 236 and a height of 360 to accommodate both in a single viewing window. If you like, you can add more ConcreteObserver classes for different displays. The Observer is a very flexible design pattern.

Share

Copyright © 2015 William Sanders. All Rights Reserved.

0 Responses to “PHP Observer Design Pattern: The Basics”


  • No Comments

Leave a Reply