In this article i will talk about one of the behavioral patterns which is the observer design pattern what it’s components and how to implement it in PHP.
Many open source php frameworks have already built in support for firing and listening to events such as symfony and laravel. Despite this all these frameworks use the observer pattern behind the scenes and each framework implement it in a different way but the concept is the same, let’s take a look at the observer pattern and it’s components.
The simplest model of the observer pattern has three main components:
- Subject: It’s the main object responsible for attaching and detaching observers, also is the main object who notify all observers if there is any data update.
- Abstract Observer: This is an abstract class or interface which takes a subject as a reference and an update() method which is called by the subject in case of any update.
- Concrete observers: Each observer extends from abstract observer and implement the update() method according to each one.
As you see in the above diagram the main components of the observer pattern can be described with one to many relationship with the fact with one subject can notify multiple observers. But how the observers notified? The answer is it must be some kind of state change which means when i update the state the observers notified of this state update. This is already applied in some technologies like angular 2 rxjs.
Now lets see the three components in action, in this example i demonstrate observer pattern to send notification using various ways like email, sms and push notification so if the user making any updates the three observer notified i remove the actual code for sending emails, sms and push notification to focus only on the idea.
Subject.php
<?php class Subject { private $observers; private $state; public function getState() { return $this->state; } public function setState($state) { $this->state = $state; $this->notify(); } public function attach(\AbstractObserver $observer) { $this->observers[spl_object_hash($observer)] = $observer; } public function detach(\AbstractObserver $observer) { $id = spl_object_hash($observer); unset($this->observers[$id]); } public function notify() { foreach($this->observers as $id => $observer) { $observer->update(); } } }
AbstractObserver.php
<?php abstract class AbstractObserver { protected $subject; public function __construct(\Subject $subject) { $this->subject = $subject; $this->subject->attach($this); } abstract public function update(); }
EmailObserver.php
<?php require_once './AbstractObserver.php'; class EmailObserver extends AbstractObserver { public function update() { // email sending code omitted for brevity echo "<br/>Sending email notification with data: " . $this->subject->getState() . "<br/>"; } }
SMSObserver.php
<?php require_once './AbstractObserver.php'; class SMSObserver extends AbstractObserver { public function update() { // sms sending code omitted for brevity echo "<br/>Sending sms notification with data: " . $this->subject->getState() . "<br/>"; } }
PushNotificationObserver.php
<?php require_once './AbstractObserver.php'; class PushNotificationObserver extends AbstractObserver { public function update() { // push notification code omitted for brevity echo "<br/>Sending push notification with data: " . $this->subject->getState() . "<br/>"; } }
In the code above the Subject class contains methods getState(), setState(), attach(), detach(), notify(). The attach() and detach() methods used to add or remove observers respectively, here i use the observer object hash as a key obtained by calling spl_object_hash() function. The notify() method loops through all the observers and call the update() method on each of them.
The AbstractObserver class is the skeleton class that all observers inherit from, in the constructor we inject the subject this observer assigned to and call attach() method passing “$this” as an instance of the current observer. The update() method is an abstract method so that we can implement it according to each observer.
Then i created three observer classes, which are EmailObserver, SMSObserver, PushNotificationObserver. All of these classes extend from AbstractObserver. Next i implemented the update() method with an echo statement just for the purposes of demonstration.
To test this example i create a simple class Client.php like this:
Client.php
<?php require_once './Subject.php'; require_once './EmailObserver.php'; require_once './SMSObserver.php'; require_once './PushNotificationObserver.php'; class Client { public function __construct() { $subject = new Subject(); $emailObserver = new EmailObserver($subject); $smsObserver = new SMSObserver($subject); $pushObserver = new PushNotificationObserver($subject); echo "<h3>Starting observers</h3>"; $subject->setState("my name: john smith"); echo "<h3>Updating state</h3>"; $subject->setState("my age: 30 years"); echo "<h3>Detaching sms observer</h3>"; $subject->detach($smsObserver); $subject->setState("my name: john smith"); } } // run client new \Client();
I created one subject and then added three observers passing the $subject to each observer constructor, then i called setState() in the subject which in turn update the state and notify all the observers. This is why the observer pattern applies one to many relationships from one subject to many observers.
SPLObserver
Since PHP 5.1 The spl extension contains dedicated interfaces for dealing with observer pattern instead of writing classes from scratch which are SplObserver and SplSubject. Those interfaces contains to some extent the same methods we saw earlier.
SplSubject Structure:
SplSubject { /* Methods */ abstract public attach ( SplObserver $observer ) : void abstract public detach ( SplObserver $observer ) : void abstract public notify ( void ) : void }
SplObserver Structure:
SplObserver { /* Methods */ abstract public update ( SplSubject $subject ) : void }
So to use the splobserver we need to implement those interfaces and implement their methods, let’s rewrite the previous example using the splobserver.
Subject.php
<?php class Subject implements SplSubject { private $observers; private $state; public function __construct() { $this->observers = new SplObjectStorage(); } public function attach(SplObserver $observer) { $this->observers->attach($observer); } public function detach(SplObserver $observer) { $this->observers->detach($observer); } public function notify() { foreach ($this->observers as $observer) { $observer->update($this); } } public function getState() { return $this->state; } public function setState($state) { $this->state = $state; $this->notify(); } }
EmailObserver.php
<?php class EmailObserver implements SplObserver { public function update(SplSubject $subject) { echo "<br/>Sending email notification with data: " . $subject->getState() . "<br/>"; } }
SMSObserver.php
<?php class SMSObserver implements SplObserver { public function update(SplSubject $subject) { echo "<br/>Sending sms notification with data: " . $subject->getState() . "<br/>"; } }
Client.php
<?php require_once './Subject.php'; require_once './EmailObserver.php'; require_once './SMSObserver.php'; class Client { public function __construct() { $subject = new Subject(); $emailObserver = new EmailObserver(); $smsObserver = new SMSObserver(); $subject->attach($emailObserver); $subject->attach($smsObserver); echo "<h3>Starting observers</h3>"; $subject->setState("my name: john smith"); echo "<h3>Updating state</h3>"; $subject->setState("my age: 30 years"); echo "<h3>Detaching sms observer</h3>"; $subject->detach($smsObserver); $subject->setState("my name: john smith"); } } // run client new \Client();
As you see the code is pretty similar to the custom observer classes we saw earlier, I used SplObjectStorage in our subject constructor this is another spl utility class that can map objects to data, this class contains methods attach() and detach() to attach or remove objects.
Real world example
A real world usage for the observer pattern is to display real time graphs for certain activity especially i you have multiple graphs updated simultaneous according to some value sent to server. Let’s see this example which i use CanvasJS, socket io and php observer pattern to render real time graphs, you have to be fimiliar with using socket io, i will display only some snippets, you will find the full source code in bitbucket repository at the bottom of the article.
Â
Dependencies needed:
- Canvasjs library
- phpsocket.io: for handling socket io with php
- socket io js client
- jquery
Create a new folder with any name with this file structure in your server root:
- js/ folder: contains javascript files
- observer/ folder: contains observer and subject classes
- composer.json
- index.html
- server.php
Next install phpsocketio with:
composer require workerman/phpsocket.io
Then prepare observer classes located in observer/ directory:
observer/Subject.php
<?php class Subject implements SplSubject { private $observers; public function __construct() { $this->observers = new SplObjectStorage(); } public function attach(SplObserver $observer) { $this->observers->attach($observer); } public function detach(SplObserver $observer) { $this->observers->detach($observer); } public function notify() { foreach ($this->observers as $observer) { $observer->update($this); } } }
Suppose we have three types of graphs (line, column, pie) so create observer class for each type
observer/LineGraphObserver.php
<?php class LineGraphObserver implements SplObserver { public function update(SplSubject $subject) { global $io; $dataPoints = []; $xVal = 0; $yVal = 100; for($i=0; $i<12; $i++) { $yVal = $yVal + floor(rand(1, 1000)); $dataPoints[] = ["x" => $xVal, "y" => $yVal]; $xVal++; $yVal = 0; } if(count($dataPoints) > 12) { array_shift($dataPoints); } // emit the data points to socket server $io->emit('lineGraph', array('dataPoints' => $dataPoints)); } }
observer/ColumnGraphObserver.php
<?php class ColumnGraphObserver implements SplObserver { public function update(SplSubject $subject) { global $io; $dataPoints = []; $xVal = 0; $yVal = 100; for($i=0; $i < 9; $i++) { $yVal = $yVal + floor(rand(1, 1000)); $dataPoints[] = ['x' => $xVal, 'y' => $yVal, 'label' => "[$xVal, $yVal]"]; $xVal++; $yVal = 0; } if(count($dataPoints) > 9) { array_shift($dataPoints); } // emit the data points $io->emit('columnGraph', array('dataPoints' => $dataPoints)); } }
observer/PieGraphObserver.php
<?php class PieGraphObserver implements SplObserver { public function update(SplSubject $subject) { global $io; $dataPoints = []; $xVal = 0; $yVal = 100; for($i=0; $i < 5; $i++) { $yVal = $yVal + floor(rand(1, 1000)); $dataPoints[] = ['x' => $xVal, 'y' => $yVal, 'label' => "[$xVal, $yVal]"]; $xVal++; $yVal = 0; } if(count($dataPoints) > 5) { array_shift($dataPoints); } // emit the data points $io->emit('pieGraph', array('dataPoints' => $dataPoints)); } }
As you see each observer update() method emit the data points required to render the graph to the client side, you may ask yourself where the variable $io comes from, it comes from server.php as you see in a moment.
server.php
<?php require_once 'vendor/autoload.php'; use Workerman\Worker; use Workerman\WebServer; use Workerman\Autoloader; use PHPSocketIO\SocketIO; $subject = new Subject(); $columnObserver = new ColumnGraphObserver(); $lineObserver = new LineGraphObserver(); $pieObserver = new PieGraphObserver(); $subject->attach($lineObserver); $subject->attach($columnObserver); $subject->attach($pieObserver); $io = new SocketIO(2020); $io->on('connection', function($socket) use ($subject) { $subject->notify(); $socket->on('refresh_graphs', function () use($subject) { $subject->notify(); }); }); Worker::runAll();
The above code initializes the subject, attach the observers to the subject then we create a new instance of SocketIO passing in the port number “2020”, this the port number that socket io server will listen to, we will use that number also when listenting to socket events from client side, you can use a different port number if that is not available.
The most important method on socket io is the on() method which takes an event name and a callback function and executes that function when that event fires. In this case when i say $io->on(‘connection’) means when sockets is active and connected then fire $subject->notify().
The same also when i say $socket->on() inside $io->on(‘connection’) means when this event occurs inside active socket connection. To learn more about phpsocketio refer to this github link.
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Real time graphs</title> <script src="js/jquery.min.js"></script> <script src="js/socket-io-client/socket-io-client.js"></script> <script src="https://canvasjs.com/assets/script/canvasjs.min.js"></script> <script src="js/main.js"></script> </head> <body> <div id="lineContainer" style="height: 300px; width: 30%; float: left"></div> <div id="columnContainer" style="height: 300px; width: 30%; float: left"></div> <div id="pieContainer" style="height: 300px; width: 30%; float: left"></div> </body> </html>
js/main.js
window.onload = function () { var charts = [ {type: 'line', selector: 'lineContainer', instance: '', dataPoints: []}, {type: 'column', selector: 'columnContainer', instance: '', dataPoints: []}, {type: 'pie', selector: 'pieContainer', instance: '', dataPoints: []} ]; var websocketport = '2020'; var updateInterval = 2000; for(var i=0; i<charts.length; i++) { charts[i].instance = new CanvasJS.Chart(charts[i].selector, { title: { text: charts[i].type + " chart" }, axisY: { includeZero: false }, data: [{ type: charts[i].type, dataPoints: charts[i].dataPoints }] }); } for (var i=0; i<charts.length; i++) { charts[i].instance.render(); } let socket = io('http://'+document.domain+':' + websocketport); socket.on('lineGraph', function(data) { clearArray(charts[0].dataPoints); for(var i=0; i<data.dataPoints.length; i++) { charts[0].dataPoints.push(data.dataPoints[i]); } charts[0].instance.render(); }); socket.on('columnGraph', function(data) { clearArray(charts[1].dataPoints); for(var i=0; i<data.dataPoints.length; i++) { charts[1].dataPoints.push(data.dataPoints[i]); } charts[1].instance.render(); }); socket.on('pieGraph', function(data) { clearArray(charts[2].dataPoints); for(var i=0; i<data.dataPoints.length; i++) { charts[2].dataPoints.push(data.dataPoints[i]); } charts[2].instance.render(); }); setInterval(function () { socket.emit('refresh_graphs', 1); }, updateInterval); clearArray = function (arr) { while(arr.length > 0) { arr.pop(); } } }
Here i displayed the three chart types and in main.js rendered them using the element id, then i invoked socket io from javascript using io() function passing the url which is “http://localhost:2020”. Note that this is the same port we mentioned earlier in server.php.
Using this socket instance i listened to events emitted from the server using io.on() function thereby renderd the graph using the response coming from the server.
To make the graphs refreshing we have to emit event to the server every particular seconds this is done with:
setInterval(function () { socket.emit('refresh_graphs', 1); }, updateInterval);
The server will catch this event and call notify observers again.
Running the example
In your project root folder start the socket server with:
php server.php start
This will display success started, if an error happened try to check the change the port number.
Then in the browser go to http://localhost/project-folder/ you will see the graphs running and updated every 2 seconds.
Download
git clone https://webmobtuts@bitbucket.org/webmobtuts/php-observer-real-time-graphs.git