--- /dev/null
+<?php
+/**
+ * Publish-Subscribe system based on Phly_PubSub
+ * (http://weierophinney.net/matthew/archives/199-A-Simple-PHP-Publish-Subscribe-System.html)
+ *
+ * @category Horde
+ * @package PubSub
+ * @copyright Copyright (C) 2008 - Present, Matthew Weier O'Phinney
+ * @author Matthew Weier O'Phinney <mweierophinney@gmail.com>
+ * @license New BSD {@link http://www.opensource.org/licenses/bsd-license.php}
+ */
+
+/**
+ * Publish-Subscribe system
+ *
+ * @category Horde
+ * @package PubSub
+ */
+class Horde_PubSub
+{
+ /**
+ * Subscribed topics and their handles
+ */
+ protected static $_topics = array();
+
+ /**
+ * Publish to all handlers for a given topic
+ *
+ * @param string $topic
+ * @param mixed $args All arguments besides the topic are passed as arguments to the handler
+ * @return void
+ */
+ public static function publish($topic, $args = null)
+ {
+ if (empty(self::$_topics[$topic])) {
+ return;
+ }
+ $args = func_get_args();
+ array_shift($args);
+ foreach (self::$_topics[$topic] as $handle) {
+ $handle->call($args);
+ }
+ }
+
+ /**
+ * Subscribe to a topic
+ *
+ * @param string $topic
+ * @param string|object $context Function name, class name, or object instance
+ * @param null|string $handler If $context is a class or object, the name of the method to call
+ * @return Horde_PubSub_Handle Pub-Sub handle (to allow later unsubscribe)
+ */
+ public static function subscribe($topic, $context, $handler = null)
+ {
+ if (empty(self::$_topics[$topic])) {
+ self::$_topics[$topic] = array();
+ }
+ $handle = new Horde_PubSub_Handle($topic, $context, $handler);
+ if (in_array($handle, self::$_topics[$topic])) {
+ $index = array_search($handle, self::$_topics[$topic]);
+ return self::$_topics[$topic][$index];
+ }
+ self::$_topics[$topic][] = $handle;
+ return $handle;
+ }
+
+ /**
+ * Unsubscribe a handler from a topic
+ *
+ * @param Horde_PubSub_Handle $handle
+ * @return bool Returns true if topic and handle found, and unsubscribed; returns false if either topic or handle not found
+ */
+ public static function unsubscribe(Horde_PubSub_Handle $handle)
+ {
+ $topic = $handle->getTopic();
+ if (empty(self::$_topics[$topic])) {
+ return false;
+ }
+ if (false === ($index = array_search($handle, self::$_topics[$topic]))) {
+ return false;
+ }
+ unset(self::$_topics[$topic][$index]);
+ return true;
+ }
+
+ /**
+ * Retrieve all registered topics
+ *
+ * @return array
+ */
+ public static function getTopics()
+ {
+ return array_keys(self::$_topics);
+ }
+
+ /**
+ * Retrieve all handlers for a given topic
+ *
+ * @param string $topic
+ * @return array Array of Horde_PubSub_Handle objects
+ */
+ public static function getSubscribedHandles($topic)
+ {
+ if (empty(self::$_topics[$topic])) {
+ return array();
+ }
+ return self::$_topics[$topic];
+ }
+
+ /**
+ * Clear all handlers for a given topic
+ *
+ * @param string $topic
+ * @return void
+ */
+ public static function clearHandles($topic)
+ {
+ if (!empty(self::$_topics[$topic])) {
+ unset(self::$_topics[$topic]);
+ }
+ }
+}
--- /dev/null
+<?php
+/**
+ * Publish-Subscribe system based on Phly_PubSub
+ * (http://weierophinney.net/matthew/archives/199-A-Simple-PHP-Publish-Subscribe-System.html)
+ *
+ * @category Horde
+ * @package PubSub
+ * @copyright Copyright (C) 2008 - Present, Matthew Weier O'Phinney
+ * @author Matthew Weier O'Phinney <mweierophinney@gmail.com>
+ * @license New BSD {@link http://www.opensource.org/licenses/bsd-license.php}
+ */
+
+/**
+ * Publish-Subscribe handler: unique handle subscribed to a given topic.
+ *
+ * @category Horde
+ * @package PubSub
+ */
+class Horde_PubSub_Handle
+{
+ /**
+ * PHP callback to invoke
+ * @var string|array
+ */
+ protected $_callback;
+
+ /**
+ * Topic to which this handle is subscribed
+ * @var string
+ */
+ protected $_topic;
+
+ /**
+ * Constructor
+ *
+ * @param string $topic Topic to which handle is subscribed
+ * @param string|object $context Function name, class name, or object instance
+ * @param string|null $handler Method name, if $context is a class or object
+ */
+ public function __construct($topic, $context, $handler = null)
+ {
+ $this->_topic = $topic;
+
+ if (null === $handler) {
+ $this->_callback = $context;
+ } else {
+ $this->_callback = array($context, $handler);
+ }
+ }
+
+ /**
+ * Get topic to which handle is subscribed
+ *
+ * @return string
+ */
+ public function getTopic()
+ {
+ return $this->_topic;
+ }
+
+ /**
+ * Retrieve registered callback
+ *
+ * @return string|array
+ */
+ public function getCallback()
+ {
+ return $this->_callback;
+ }
+
+ /**
+ * Invoke handler
+ *
+ * @param array $args Arguments to pass to callback
+ * @return void
+ */
+ public function call(array $args)
+ {
+ call_user_func_array($this->getCallback(), $args);
+ }
+}
--- /dev/null
+<?php
+/**
+ * Publish-Subscribe system based on Phly_PubSub
+ * (http://weierophinney.net/matthew/archives/199-A-Simple-PHP-Publish-Subscribe-System.html)
+ *
+ * @category Horde
+ * @package PubSub
+ * @copyright Copyright (C) 2008 - Present, Matthew Weier O'Phinney
+ * @author Matthew Weier O'Phinney <mweierophinney@gmail.com>
+ * @license New BSD {@link http://www.opensource.org/licenses/bsd-license.php}
+ */
+
+/**
+ * Publish-Subscribe provider
+ *
+ * Use Horde_PubSub_Provider when you want to create a per-instance plugin
+ * system for your objects.
+ *
+ * @category Horde
+ * @package PubSub
+ */
+class Horde_PubSub_Provider
+{
+ /**
+ * Subscribed topics and their handles
+ */
+ protected $_topics = array();
+
+ /**
+ * Publish to all handlers for a given topic
+ *
+ * @param string $topic
+ * @param mixed $args All arguments besides the topic are passed as arguments to the handler
+ * @return void
+ */
+ public function publish($topic, $args = null)
+ {
+ if (empty($this->_topics[$topic])) {
+ return;
+ }
+ $args = func_get_args();
+ array_shift($args);
+ foreach ($this->_topics[$topic] as $handle) {
+ $handle->call($args);
+ }
+ }
+
+ /**
+ * Subscribe to a topic
+ *
+ * @param string $topic
+ * @param string|object $context Function name, class name, or object instance
+ * @param null|string $handler If $context is a class or object, the name of the method to call
+ * @return Horde_PubSub_Handle Pub-Sub handle (to allow later unsubscribe)
+ */
+ public function subscribe($topic, $context, $handler = null)
+ {
+ if (empty($this->_topics[$topic])) {
+ $this->_topics[$topic] = array();
+ }
+ $handle = new Horde_PubSub_Handle($topic, $context, $handler);
+ if (in_array($handle, $this->_topics[$topic])) {
+ $index = array_search($handle, $this->_topics[$topic]);
+ return $this->_topics[$topic][$index];
+ }
+ $this->_topics[$topic][] = $handle;
+ return $handle;
+ }
+
+ /**
+ * Unsubscribe a handler from a topic
+ *
+ * @param Horde_PubSub_Handle $handle
+ * @return bool Returns true if topic and handle found, and unsubscribed; returns false if either topic or handle not found
+ */
+ public function unsubscribe(Horde_PubSub_Handle $handle)
+ {
+ $topic = $handle->getTopic();
+ if (empty($this->_topics[$topic])) {
+ return false;
+ }
+ if (false === ($index = array_search($handle, $this->_topics[$topic]))) {
+ return false;
+ }
+ unset($this->_topics[$topic][$index]);
+ return true;
+ }
+
+ /**
+ * Retrieve all registered topics
+ *
+ * @return array
+ */
+ public function getTopics()
+ {
+ return array_keys($this->_topics);
+ }
+
+ /**
+ * Retrieve all handlers for a given topic
+ *
+ * @param string $topic
+ * @return array Array of Horde_PubSub_Handle objects
+ */
+ public function getSubscribedHandles($topic)
+ {
+ if (empty($this->_topics[$topic])) {
+ return array();
+ }
+ return $this->_topics[$topic];
+ }
+
+ /**
+ * Clear all handlers for a given topic
+ *
+ * @param string $topic
+ * @return void
+ */
+ public function clearHandles($topic)
+ {
+ if (!empty($this->_topics[$topic])) {
+ unset($this->_topics[$topic]);
+ }
+ }
+}
--- /dev/null
+<?php
+/**
+ * All tests for the Horde_PubSub:: package.
+ *
+ * PHP version 5
+ *
+ * @category Horde
+ * @package PubSub
+ * @author Gunnar Wrobel <wrobel@pardus.de>
+ * @license http://www.fsf.org/copyleft/lgpl.html LGPL
+ * @link http://pear.horde.org/index.php?package=PubSub
+ */
+
+/**
+ * Define the main method
+ */
+if (!defined('PHPUnit_MAIN_METHOD')) {
+ define('PHPUnit_MAIN_METHOD', 'Horde_PubSub_AllTests::main');
+}
+
+/**
+ * Prepare the test setup.
+ */
+require_once 'Horde/Test/AllTests.php';
+
+/**
+ * @package Horde_PubSub
+ * @subpackage UnitTests
+ */
+class Horde_PubSub_AllTests extends Horde_Test_AllTests
+{
+}
+
+Horde_PubSub_AllTests::init('Horde_PubSub', __FILE__);
+
+if (PHPUnit_MAIN_METHOD == 'Horde_PubSub_AllTests::main') {
+ Horde_PubSub_AllTests::main();
+}
--- /dev/null
+<?php
+/**
+ * @category Horde
+ * @package PubSub
+ * @subpackage UnitTests
+ * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license New BSD {@link http://www.opensource.org/licenses/bsd-license.php}
+ */
+class Horde_PubSub_HandleTest extends Horde_Test_Case
+{
+ public function setUp()
+ {
+ if (isset($this->args)) {
+ unset($this->args);
+ }
+ }
+
+ public function testGetTopicShouldReturnTopic()
+ {
+ $handle = new Horde_PubSub_Handle('foo', 'bar');
+ $this->assertEquals('foo', $handle->getTopic());
+ }
+
+ public function testCallbackShouldBeStringIfNoHandlerPassedToConstructor()
+ {
+ $handle = new Horde_PubSub_Handle('foo', 'bar');
+ $this->assertSame('bar', $handle->getCallback());
+ }
+
+ public function testCallbackShouldBeArrayIfHandlerPassedToConstructor()
+ {
+ $handle = new Horde_PubSub_Handle('foo', 'bar', 'baz');
+ $this->assertSame(array('bar', 'baz'), $handle->getCallback());
+ }
+
+ public function testCallShouldInvokeCallbackWithSuppliedArguments()
+ {
+ $handle = new Horde_PubSub_Handle('foo', $this, 'handleCall');
+ $args = array('foo', 'bar', 'baz');
+ $handle->call($args);
+ $this->assertSame($args, $this->args);
+ }
+
+ public function handleCall()
+ {
+ $this->args = func_get_args();
+ }
+}
--- /dev/null
+<?php
+/**
+ * @category Horde
+ * @package PubSub
+ * @subpackage UnitTests
+ * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license New BSD {@link http://www.opensource.org/licenses/bsd-license.php}
+ */
+class Horde_PubSub_ProviderTest extends Horde_Test_Case
+{
+ public function setUp()
+ {
+ if (isset($this->message)) {
+ unset($this->message);
+ }
+ $this->provider = new Horde_PubSub_Provider;
+ }
+
+ public function testSubscribeShouldReturnHandle()
+ {
+ $handle = $this->provider->subscribe('test', $this, __METHOD__);
+ $this->assertTrue($handle instanceof Horde_PubSub_Handle);
+ }
+
+ public function testSubscribeShouldAddHandleToTopic()
+ {
+ $handle = $this->provider->subscribe('test', $this, __METHOD__);
+ $handles = $this->provider->getSubscribedHandles('test');
+ $this->assertEquals(1, count($handles));
+ $this->assertContains($handle, $handles);
+ }
+
+ public function testSubscribeShouldAddTopicIfItDoesNotExist()
+ {
+ $topics = $this->provider->getTopics();
+ $this->assertTrue(empty($topics), var_export($topics, 1));
+ $handle = $this->provider->subscribe('test', $this, __METHOD__);
+ $topics = $this->provider->getTopics();
+ $this->assertFalse(empty($topics));
+ $this->assertContains('test', $topics);
+ }
+
+ public function testUnsubscribeShouldRemoveHandleFromTopic()
+ {
+ $handle = $this->provider->subscribe('test', $this, __METHOD__);
+ $handles = $this->provider->getSubscribedHandles('test');
+ $this->assertContains($handle, $handles);
+ $this->provider->unsubscribe($handle);
+ $handles = $this->provider->getSubscribedHandles('test');
+ $this->assertNotContains($handle, $handles);
+ }
+
+ public function testUnsubscribeShouldReturnFalseIfTopicDoesNotExist()
+ {
+ $handle = $this->provider->subscribe('test', $this, __METHOD__);
+ $this->provider->clearHandles('test');
+ $this->assertFalse($this->provider->unsubscribe($handle));
+ }
+
+ public function testUnsubscribeShouldReturnFalseIfHandleDoesNotExist()
+ {
+ $handle1 = $this->provider->subscribe('test', $this, __METHOD__);
+ $this->provider->clearHandles('test');
+ $handle2 = $this->provider->subscribe('test', $this, 'handleTestTopic');
+ $this->assertFalse($this->provider->unsubscribe($handle1));
+ }
+
+ public function testRetrievingSubscribedHandlesShouldReturnEmptyArrayWhenTopicDoesNotExist()
+ {
+ $handles = $this->provider->getSubscribedHandles('test');
+ $this->assertTrue(empty($handles));
+ }
+
+ public function testPublishShouldNotifySubscribedHandlers()
+ {
+ $handle = $this->provider->subscribe('test', $this, 'handleTestTopic');
+ $this->provider->publish('test', 'test message');
+ $this->assertEquals('test message', $this->message);
+ }
+
+ public function handleTestTopic($message)
+ {
+ $this->message = $message;
+ }
+}
--- /dev/null
+<?php
+/**
+ * @category Horde
+ * @package PubSub
+ * @subpackage UnitTests
+ * @copyright Copyright (c) 2005-2008 Zend Technologies USA Inc. (http://www.zend.com)
+ * @license New BSD {@link http://www.opensource.org/licenses/bsd-license.php}
+ */
+class Horde_PubSub_PubSubTest extends Horde_Test_Case
+{
+ public function setUp()
+ {
+ if (isset($this->message)) {
+ unset($this->message);
+ }
+ $this->clearAllTopics();
+ }
+
+ public function tearDown()
+ {
+ $this->clearAllTopics();
+ }
+
+ public function clearAllTopics()
+ {
+ $topics = Horde_PubSub::getTopics();
+ foreach ($topics as $topic) {
+ Horde_PubSub::clearHandles($topic);
+ }
+ }
+
+ public function testSubscribeShouldReturnHandle()
+ {
+ $handle = Horde_PubSub::subscribe('test', $this, __METHOD__);
+ $this->assertTrue($handle instanceof Horde_PubSub_Handle);
+ }
+
+ public function testSubscribeShouldAddHandleToTopic()
+ {
+ $handle = Horde_PubSub::subscribe('test', $this, __METHOD__);
+ $handles = Horde_PubSub::getSubscribedHandles('test');
+ $this->assertEquals(1, count($handles));
+ $this->assertContains($handle, $handles);
+ }
+
+ public function testSubscribeShouldAddTopicIfItDoesNotExist()
+ {
+ $topics = Horde_PubSub::getTopics();
+ $this->assertTrue(empty($topics), var_export($topics, 1));
+ $handle = Horde_PubSub::subscribe('test', $this, __METHOD__);
+ $topics = Horde_PubSub::getTopics();
+ $this->assertFalse(empty($topics));
+ $this->assertContains('test', $topics);
+ }
+
+ public function testUnsubscribeShouldRemoveHandleFromTopic()
+ {
+ $handle = Horde_PubSub::subscribe('test', $this, __METHOD__);
+ $handles = Horde_PubSub::getSubscribedHandles('test');
+ $this->assertContains($handle, $handles);
+ Horde_PubSub::unsubscribe($handle);
+ $handles = Horde_PubSub::getSubscribedHandles('test');
+ $this->assertNotContains($handle, $handles);
+ }
+
+ public function testUnsubscribeShouldReturnFalseIfTopicDoesNotExist()
+ {
+ $handle = Horde_PubSub::subscribe('test', $this, __METHOD__);
+ Horde_PubSub::clearHandles('test');
+ $this->assertFalse(Horde_PubSub::unsubscribe($handle));
+ }
+
+ public function testUnsubscribeShouldReturnFalseIfHandleDoesNotExist()
+ {
+ $handle1 = Horde_PubSub::subscribe('test', $this, __METHOD__);
+ Horde_PubSub::clearHandles('test');
+ $handle2 = Horde_PubSub::subscribe('test', $this, 'handleTestTopic');
+ $this->assertFalse(Horde_PubSub::unsubscribe($handle1));
+ }
+
+ public function testRetrievingSubscribedHandlesShouldReturnEmptyArrayWhenTopicDoesNotExist()
+ {
+ $handles = Horde_PubSub::getSubscribedHandles('test');
+ $this->assertTrue(empty($handles));
+ }
+
+ public function testPublishShouldNotifySubscribedHandlers()
+ {
+ $handle = Horde_PubSub::subscribe('test', $this, 'handleTestTopic');
+ Horde_PubSub::publish('test', 'test message');
+ $this->assertEquals('test message', $this->message);
+ }
+
+ public function handleTestTopic($message)
+ {
+ $this->message = $message;
+ }
+}