The Publish/Subscribe (Pub/Sub) design pattern often forms the basis of software event-driven programming. This methodology allows asynchronous communications between different software applications, or different software modules within a single application. The purpose of the pattern is to allow a method or function to publish a signal when an action of significance has taken place. One or more classes would then subscribe and take action if a certain signal has been published.
Example of such actions are when the database is modified, or when a user has logged in. Another common use for this design pattern is when an application delivers news feeds. If an urgent news item has been posted, the application would publish this fact, allowing client subscribers to refresh their news listings.
Application\PubSub\Publisher. You'll notice that we are making use of two useful Standard PHP Library (SPL) interfaces, SplSubject and SplObserver:namespace Application\PubSub;
use SplSubject;
use SplObserver;
class Publisher implements SplSubject
{
// code
}protected $name; protected $data; protected $linked; protected $subscribers;
__toString() in case we need quick access to the name of this publisher:public function __construct($name)
{
$this->name = $name;
$this->data = array();
$this->subscribers = array();
$this->linked = array();
}
public function __toString()
{
return $this->name;
}attach(), which is specified in the SplSubject interface. We accept an SplObserver instance as an argument. Note that we need to add entries to both the $subscribers and $linked properties. $linked is then sorted by value, represented by the priority, using arsort(), which sorts in reverse and maintains the key:public function attach(SplObserver $subscriber)
{
$this->subscribers[$subscriber->getKey()] = $subscriber;
$this->linked[$subscriber->getKey()] =
$subscriber->getPriority();
arsort($this->linked);
}detach(), which removes the subscriber from the list:public function detach(SplObserver $subscriber)
{
unset($this->subscribers[$subscriber->getKey()]);
unset($this->linked[$subscriber->getKey()]);
}notify(), which calls update() on all the subscribers. Note that we loop through the linked list to ensure the subscribers are called in order of priority:public function notify()
{
foreach ($this->linked as $key => $value)
{
$this->subscribers[$key]->update($this);
}
}public function getName()
{
return $this->name;
}
public function setName($name)
{
$this->name = $name;
}notify() is invoked:public function setDataByKey($key, $value)
{
$this->data[$key] = $value;
}Application\PubSub\Subscriber. Typically, we would define multiple subscribers for each publisher. In this case, we implement the SplObserver interface:namespace Application\PubSub;
use SplSubject;
use SplObserver;
class Subscriber implements SplObserver
{
// code
}md5() and date/time information, combined with a random number. The constructor initializes the properties as follows. The actual logical functionality performed by the subscriber is in the form of a callback:protected $key;
protected $name;
protected $priority;
protected $callback;
public function __construct(
string $name, callable $callback, $priority = 0)
{
$this->key = md5(date('YmdHis') . rand(0,9999));
$this->name = $name;
$this->callback = $callback;
$this->priority = $priority;
}update() function is called when notifiy() on the publisher is invoked. We pass a publisher instance as an argument, and call the callback defined for this subscriber:public function update(SplSubject $publisher)
{
call_user_func($this->callback, $publisher);
}public function getKey()
{
return $this->key;
}
public function setKey($key)
{
$this->key = $key;
}
// other getters and setters not shownFor the purposes of this illustration, define a calling program called chap_11_pub_sub_simple_example.php, which sets up autoloading and uses the appropriate classes:
<?php
require __DIR__ . '/../Application/Autoload/Loader.php';
Application\Autoload\Loader::init(__DIR__ . '/..');
use Application\PubSub\ { Publisher, Subscriber };Next, create a publisher instance and assign data:
$pub = new Publisher('test');
$pub->setDataByKey('1', 'AAA');
$pub->setDataByKey('2', 'BBB');
$pub->setDataByKey('3', 'CCC');
$pub->setDataByKey('4', 'DDD');Now you can create test subscribers that read data from the publisher and echo the results. The first parameter is the name, the second the callback, and the last is the priority:
$sub1 = new Subscriber(
'1',
function ($pub) {
echo '1:' . $pub->getData()[1] . PHP_EOL;
},
10
);
$sub2 = new Subscriber(
'2',
function ($pub) {
echo '2:' . $pub->getData()[2] . PHP_EOL;
},
20
);
$sub3 = new Subscriber(
'3',
function ($pub) {
echo '3:' . $pub->getData()[3] . PHP_EOL;
},
99
);For test purposes, attach the subscribers out of order, and call notify() twice:
$pub->attach($sub2); $pub->attach($sub1); $pub->attach($sub3); $pub->notify(); $pub->notify();
Next, define and attach another subscriber that looks at the data for subscriber 1 and exits if it's not empty:
$sub4 = new Subscriber(
'4',
function ($pub) {
echo '4:' . $pub->getData()[4] . PHP_EOL;
if (!empty($pub->getData()[1]))
die('1 is set ... halting execution');
},
25
);
$pub->attach($sub4);
$pub->notify();Here is the output. Note that the output is in order of priority (where higher priority goes first), and that the second block of output is interrupted:

A closely related software design pattern is Observer. The mechanism is similar but the generally agreed difference is that Observer operates in a synchronous manner, where all observer methods are called when a signal (often also referred to as message or event) is received. The Pub/Sub pattern, in contrast, operates asynchronously, typically using a message queue. Another difference is that in the Pub/Sub pattern, publishers do not need to be aware of subscribers.
For a good discussion on the difference between the Observer and Pub/Sub patterns, refer to the article at http://stackoverflow.com/questions/15594905/difference-between-observer-pub-sub-and-data-binding.