LogoPhly, boy, phly
the weblog and site of Matthew Weier O'Phinney

Monday, December 15. 2008

A Simple PHP Publish-Subscribe System

I've been playing a lot with Dojo lately, and have been very impressed by its elegant publish-subscribe system. Basically, any object can publish an event, and any other object can subscribe to it. This creates an incredibly flexible notification architecture that's completely opt-in.

The system has elements of Aspect Oriented Programming (AOP), as well as the Observer pattern. Its power, however, is in the fact that an individual object does not need to implement any specific interface in order to act as either a Subject or an Observer; the system is globally available.

Being a developer who recognizes good ideas when he sees them, of course I decided to port the idea to PHP. You can see the results on github.

Usage is incredibly simple: an object publishes an event, which triggers all subscribers.

Probably the most illustrative solution would be for optionally logging. Say for instance that you create a logger instance in your application bootstrap; you could then subscribe it to all "log" events:


$log = new Zend_Log(new Zend_Log_Writer_Stream('/tmp/application.log'));
Phly_PubSub::subscribe('log', $log, 'info');
 

Then, in your code, whenever you might want to log some information, simply publish to the "log" topic:


Phly_PubSub::publish('log', 'Log message...');
 

In production, you could simply comment out the log definition and subscription, disabling logging throughout the application. Events that publish to topics without subscribers simply return early -- meaning no ramifications for code that uses the system. You could then enable the logger at will when you need to debug or determine what events are triggering.

As another example, consider a model that has a save method. You may want to log the data sent to it, as well as the id returned. Additionally, you may want to update your search index and caches once the item has been saved to your persistence store.

Your model's save method might then look like this:


class Foo
{
    public function save(array $data)
    {
        Phly_PubSub::publish('Foo::save::start', $data, $this);

        // ...

        Phly_PubSub::publish('Foo::save::end', $id, $this);
        return $id;
    }
}
 

Elsewhere, you may have defined your logger, indexer, and cache. Where those are defined, you would tell them what topics you're subscribing each to.


Phly_PubSub::subscribe('Foo::save::start', $logger, 'logSaveData');
Phly_PubSub::subscribe('Foo::save::end', $logger, 'logSaveId');
Phly_PubSub::subscribe('Foo::save::end', $cache, 'updateFooItem');
Phly_PubSub::subscribe('Foo::save::end', $index, 'updateFooItem');
 

The beauty of the approach is the simplicity: Foo doesn't need to implement it's own pub/sub interface -- in fact, if Foo already existed in your application, you could drop in this functionality trivially. On the other side of the coin, if you have no subscribers to the events, there are no drawbacks.

Some places it could be improved:

  • The ability for return values could be useful, to allow interruption of method execution or to modify arguments sent by the publisher. However, since each topic may have multiple handlers, a simple interface would be difficult to achieve.
  • Exception handling. In most cases, you probably don't want method execution to halt due to a subscriber raising an exception. However, you still need some way to report such errors.

I'm excited to see what uses you may be able to put this to; drop me a line if you start using it!

Update (2008-12-30): Based on some of the comments to this post, I created Phly_PubSub_Provider, which is a non-static implementation that can be attached to individual classes -- basically providing a per-object plugin system. Usage is as follows:


class Foo
{
    protected $_plugins;

    public function __construct()
    {
        $this->_plugins = new Phly_PubSub_Provider();
    }

    public function getPluginProvider()
    {
        return $this->_plugins;
    }

    public function bar()
    {
        $this->_plugins->publish('bar');
    }
}

$foo = new Foo();

// Subscribe echo() to the 'bar' event:
$foo->getPluginProvider()->subscribe('bar', 'echo');

$foo->bar(); // echo's 'bar'
 
Posted by Matthew Weier O'Phinney in PHP, Dojo at 10:26 | Comments (23) | Trackbacks (0)

Trackbacks
Trackback specific URI for this entry

No Trackbacks

Comments
Display comments as (Linear | Threaded)

Awesome!

Been planning to do PubSub in PHP research too. But in the more distributed sense, this one is limited to one script, whereas I was planning to do it multiprocess (as usual pub/sub architectures do).

I was wondering though, which elements of AOP do you see in this?
#1 RubenV (Link) on 2008-12-15 11:57 (Reply)
Basically, this sort of system defines a loose framework for specifying pointcuts and publishing advice, to use AOP terminology.
#1.1 Matthew Weier O'Phinney (Link) on 2008-12-15 18:09 (Reply)
This would work well as the basis for a plug-in system, right? Hooks in the system would be simply places where the code "published" a hook and the plugins could all just subscribe to the hooks.

I guess it abstracts something like logging one level. Instead of saying "log event here," you are simply saying "event here" and it is the consumer that does something -- the beauty is that it can be multiple consumers. Nice.
#2 Peter Keane (Link) on 2008-12-15 12:53 (Reply)
You hit the nail on the head -- it's a hook system that can be tested once and used anywhere. Like dojo.publish/dojo.subscribe, you simply need to indicate what events you're publishing (and what arguments are being provided), and you can subscribe from anywhere.
#2.1 Matthew Weier O'Phinney (Link) on 2008-12-15 18:04 (Reply)
I too have been looking into writing a PHP solution for this, working with a lot of javascript and particularly dojo has left me wanting more than the standard observer pattern in PHP.
#3 Dave Marshall (Link) on 2008-12-15 16:13 (Reply)
Why do the signals have to be static only? I would registering to static signals optional. ezcSignalSlot differs great from static and dynamic implementations. From an OO perspective having your classes clustered with static function calls sucks, even if you don't have to register anything for testability, you still have to reset them all the time.
#4 beberlei (Link) on 2008-12-15 17:12 (Reply)
Why static only? Because I want to be able to subcribe to events from anywhere, without necessarily knowing what instances are in play. SignalSlot is a great class -- but it is more limited in scope, as it can only provide signals for the scope it is in (i.e., the class to which it belongs, or the scope in which the instance exists).

Let's be honest, this is PHP; our instances are not long running, and it's *rare* that you'd need to reset PubSub other than for testing. And resetting it is trivial. Using static scoping in this case actually does a ton to separate programmatic concerns, and provides a simple interface that can be dropped in at will.
#4.1 Matthew Weier O'Phinney (Link) on 2008-12-15 18:03 (Reply)
i like your implementation more then signalslot,
mainly because of the simple way to use it.

good and, the important thing, simple implementation.
i used an similar implemenation for an small portalframework but never released it.

maybe i will use it in my next project.
#4.1.1 Martin Holzhauer (Link) on 2008-12-16 08:24 (Reply)
You may want to look at the granddaddy of publish and subscribe, TIBCO's messaging APIs and servers. See http://www.tibco.com/software/messaging/ for details.
#5 steve on 2008-12-16 04:37 (Reply)
Yeah, that stuff, and systems like JMS, is pretty cool -- but well beyond the scope of what I was trying to achieve. :-)
#5.1 Matthew Weier O'Phinney (Link) on 2008-12-16 06:13 (Reply)
This is probably bound to be asked some time, so I suppose I'll do it...

Any plans to integrate this into ZF?
#6 Vincent (Link) on 2008-12-16 10:05 (Reply)
Potentially. I want to address the return results and exception handling questions first, and then I'll consider proposing it to ZF.
#6.1 Matthew Weier O'Phinney (Link) on 2008-12-16 11:05 (Reply)
I had the same question -- ZF (seem like a natural)?
#7 Peter Keane (Link) on 2008-12-16 10:56 (Reply)
This seems like hidden dependencies everywhere in the code.
#8 Koen on 2008-12-16 12:52 (Reply)
How so? With the Subject/Observer pattern, the subject is unaware of its observers (or if it even has any) -- it simply sends a notification message. Observers may or may not be aware of what they are observing. What is important is coordinating the message sent to ensure that the observer knows what to expect. This can be done via API docs.

Again, this would be an opt-in system. Publishers publish messages without knowing what, if any, subscribers are listening. Similarly, subscribers subscribe to a message without any guarantee that anything will publish to it. It's complete de-couplization.
#8.1 Matthew Weier O'Phinney (Link) on 2008-12-16 13:13 (Reply)
The pattern itself is de-coupling. But the way it is implemented not imho. Every observer/observable depends on the pubSub class without that being clear from the interface.
#8.1.1 Koen on 2008-12-16 13:47 (Reply)
Well, the observer doesn't necessarily, but the observable would.

I'm thinking of having two variants: the current, global one, and an instantiable one that would be attached to an object. This would give a standard plugin system to objects that could benefit from them, using a standard interface, and address concerns like those you have indicated.
#8.1.1.1 Matthew Weier O'Phinney (Link) on 2008-12-16 14:02 (Reply)
I remember Ralph's comment about this:

"I think the real reason why a push based notification system is not included in SPL is b/c it (on its own) is not condusive to the nature of php. Java is more suited for the observer pattern"

Well, you've just proved him wrong.
#9 Federico on 2008-12-20 13:46 (Reply)
Sorry for being late to the game, but I'd like to echo the comments above on the nice job and thank you for sharing this. I like the flexibility and simplicity of your implementation. I just wish I would have found your post before writing something similar. Looks like I have some changes to make. :-)

For what it's worth, here is my take on PHP publish-subscribe:
http://www.threadaffinity.com/blog/2008/12/publishsubscribe-implementation-with-the-zend-framework/
#10 kevin (Link) on 2008-12-29 23:29 (Reply)
At first it seems like a nice and simple solution for some problems.
But like with e.g. design patterns it is a must to tell where the drawbacks are. And I do not mean the technical ones but the logical. This PubSub systems will lead many (php) programmers to another merely technical solution of logical problems in their applications.
And therefore I always fight against all solutions that may be technically interesting but lead to anaemic models and brains.

If you write "On the other side of the coin, if you have no subscribers to the events, there are no drawbacks. " then this is eqiuvalent: I call a method/service but it's possible that no one will hear me, like calling doNothing() or doSomethingIfYouAreThere().
I don't think that is desired in a (complex) application. Take logging: this would mean you publish a log signal and if no subscriber is there to receive it, well, nothing would happen.
What kind of control of an application this is, is that what you really want? I would use a wrapper/adapter that calls the desired method and wraps it with e.g. logging. The difference: I control what happens and what not and it is expressed in the code.

Nevertheless it's a good post, because there is a filed where PubSub may be helpful: the ntofication/event handling between different layers e.g. propagating events from domain to ui layer.
So I see it as a must to tell people where to use it and where not, because of the possible drawbacks leading to non-understandable applications with some technical voodoo but not readable and logic code.
#11 Don on 2008-12-31 06:12 (Reply)
Don, I completely understand where you're coming from. The logging example is perhaps a bit naive, but it's one that easily understandable. The idea behind a pubsub system is that *if* there are subscribers, they will be notified. You should never assume that _any_ subscribers are attached. If you publish a "log" event, there should be no assumption that anything will happen; this is useful for when you want to have _optional_ logging -- for instance, when debugging. If you want to have non-optional logging, you should attach a logger, and not use a pubsub system.

My intention for this all along, however, has been for objects to publish unique events, which allows for targetted subscribers. I've also created a version that is non-static -- which ensures that no collisions occur for events published by an object to which a specific instance of the provider is attached. This basically allows you to create a plugin system for your class without going to any extra effort. Again, however, plugin systems are *always* opt-in -- there should never be an assumption that any plugins are attached.
#11.1 Matthew Weier O'Phinney (Link) on 2008-12-31 07:15 (Reply)
I fully understand your motivation for this post and appreciate your work in general. And by your reply, giving a non-static version and with your comments there are surely many good examples of how to use this.

My intention for the reply was only to be a bit more careful when chosing examples: not only your logging example (you say right, a bit naive) in this post is tempting but most examples suffer from being to too cursory. I know that from my long-year learning curve :-)
Would have been an easier time when the authors would have warned me about poor or false usage.

I will try to implement it in my current layered app for propagating events from the domain to the UI, it's indeed an elegant but also simple solution. Thanks for that.

And not to forget: have a succesful and healthy new year 2009 and keep up with your good work!
;-)
#11.1.1 Don on 2008-12-31 07:43 (Reply)
I agree with Don and Matthew that the logging example is a simple and rather naive one, but that it is also easily understandable. It's the most commonly used example I've seen to explain the nature of AOP, probably for that reason.

Matthew, thanks again for taking the time to release your work, share it with us in this blog post, and discuss it with us in comments. You rock!
#12 Matthew Turland (Link) on 2009-01-19 15:08 (Reply)

Add Comment

Standard emoticons like :-) and ;-) are converted to images.
E-Mail addresses will not be displayed and will only be used for E-Mail notifications

To prevent automated Bots from commentspamming, please enter the string you see in the image below in the appropriate input box. Your comment will only be submitted if the strings match. Please ensure that your browser supports and accepts cookies, or your comment cannot be verified correctly.
CAPTCHA

 
 
  • Home
  • Resume
  • Blog
  • Phly PEAR Channel
  • Contact Me
  • About this site

ZCE

Zend Education Advisory Board Member

Add to Technorati Favorites

Calendar

Back July '09
Mon Tue Wed Thu Fri Sat Sun
    1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31    

Quicksearch

Links

  • PHLY - PHp LibrarY
  • Paul M. Jones
  • Mike Naberezny
  • Shahar Evron
  • Planet PHP
  • Zend Where I now work
  • Garden.org Where I once worked

Archives

July 2009
June 2009
May 2009
Recent...
Older...

Categories

XML Linux
XML Personal
XML Aikido
XML Family
XML Programming
XML Dojo
XML Perl
XML PHP

All categories

Syndicate This Blog

XML RSS 0.91 feed
XML RSS 1.0 feed
XML RSS 2.0 feed
ATOM/XML ATOM 0.3 feed
ATOM/XML ATOM 1.0 feed
XML RSS 2.0 Comments

Show tagged entries

xml best practices
xml books
xml conferences
xml decorators
xml dojo
xml dpc08
xml file_fortune
xml linux
xml mvc
xml oop
xml pear
xml perl
xml personal
xml php
xml phpworks08
xml programming
xml ubuntu
xml vim
xml webinar
xml zendcon
xml zendcon08
xml zend framework
© 2004 - present, Matthew Weier O'Phinney
matthew-web <at> weierophinney.net