A Simple Design for Organizing PHP Callbacks using Publish-Subscribe: Kestrel PubSub
I’ve been rethinking an observer pattern I frequently employ and attempting to refactor the logic as a separate library. It quickly occurred to me that the enterprise publish / subscribe (PubSub) pattern was essentially applicable to callback routines. As a fun little experiment I decided to factor out each observable as a subscriber and to provide the publish and subscribe methods in a simple static class. What resulted was Kestrel PubSub; a static singleton class that illustrates this simple, albeit powerful, pattern.
Suppose, for a minute, you have the method “postError” on the object “$client” which is of type “RESTClient” and the static method “log” on the class “Logger”. To nicely wrap up both of these to receive a publish action denoted by the subject, say “invalid_password”, we would do the following:
// Setup the REST client
$client = new RESTClient('127.0.0.1', 80);
// Subscribe the REST client
Kestrel_PubSub::subscribe('invalid_password', $client, 'postError');
// Subscribe the Logger
Kestrel_PubSub::subscribe('invalid_password', 'Logger', 'log');
Later in the code where we want to publish to our subscribers, the string argument ‘Invalid password supplied’, we then simply do the following:
Kestrel_PubSub::publish('invalid_password', 'Invalid password supplied.');
That’s it! There are a few other API methods for managing these subscriptions, but shown here is the simplest use case. I should mention to those interested in this pattern that Matthew Weier O’Phinney’s A Simple PHP Publish-Subscribe System has a markedly similar implementation to this one and is worth researching depending on your needs. The one I have shown here, Kestrel PubSub, is a bare-bones implementation with very little object overhead and kept intentionally simplistic. The main difference of note between the two implementations, is that Matthew’s implementation returns a subscriber handle while mine does not. This is a nice convenience if you expect to remove subscribers, but I have yet to come up with the use case practically. Likewise discarding this convenience can greatly increase the simplicity and clarity. I did decide, at the last minute, to include an unsubscribe but instead of using a handle to unsubscribe a subscriber the unscubscribe method takes the original context in which the subscriber was added to remove it from the subscribers. I feel this is a very healthy compromise, but that could be subject to change as I continue to expand use of this pattern.




