Monday, December 15. 2008A Simple PHP Publish-Subscribe SystemI'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
Your model's 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:
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 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' 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? Basically, this sort of system defines a loose framework for specifying pointcuts and publishing advice, to use AOP terminology.
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. 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.
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.
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.
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. 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. 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.
Yeah, that stuff, and systems like JMS, is pretty cool -- but well beyond the scope of what I was trying to achieve.
This is probably bound to be asked some time, so I suppose I'll do it...
Any plans to integrate this into ZF? Potentially. I want to address the return results and exception handling questions first, and then I'll consider proposing it to ZF.
I had the same question -- ZF (seem like a natural)?
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. 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.
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. 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. 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/ 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. 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. 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! 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! Three things I would like to see added:
Ability to have hierarchical messages, such that subscribing to "Foo::" would receive any publishes that start with "Foo::" (perhaps a strict delimiter like :: should be reserved for this) Phly_PubSub::publish('Foo::save::start', $data, $this); Phly_PubSub::publish('Foo::save::end', $id, $this); Phly_PubSub::subscribe('Foo::', $logger, 'logAll'); Phly_PubSub::subscribe('Foo::save::end', $cache, 'updateFooItem'); Phly_PubSub::subscribe('Foo::save::end', $index, 'updateFooItem'); Another thing is a return value. Perhaps even a return array of the names of subscribers which interacted. This way I can say if (! Phly_PubSub::publish('foo:save')) { throw exception (' nobody listened to me')); } The last thing would be a one-to-many or multicast option. One style of publishing would find a subscriber and stop after 1 subscriber consumed it (maybe return FALSE from the subscriber to signal consuming) the other type would always deliver to all registered subscribers. This would allow for a "chain of command" style pattern where a set of plugins could all listen to one event, but could claim that event as handled by consuming the signal, thus ending the need for other plugins to be notified. Interesting timing on your requests -- I added the latter two just yesterday morning.
Not sure about the first; it's not a common pattern in the various pubsub/event architectures I've reviewed. It would be interesting -- the only worry I'd have is that different publish events often have different numbers of arguments, which could be problematic if the same subscriber is called in different contexts. Add Comment
|
Calendar
QuicksearchLinks
CategoriesSyndicate This BlogShow tagged entries |
||||||||||||||||||||||||||||||||||||||||||





