cvs: pear /Event_Dispatcher Dispatcher.php Notification.php package.xml /Event_Dispatcher/examples bubbling.php cancel.php notification-class.php
object.php /Event_Dispatcher/tests Console_TestListener.php Dispatcher_testcase.php test.php
| From: | Bertrand Mansion | Date: | Sat, 05 Feb 2005 13:10:38 +0000 |
| Subject: | cvs: pear /Event_Dispatcher Dispatcher.php Notification.php package.xml /Event_Dispatcher/examples bubbling.php cancel.php notification-class.php object.php /Event_Dispatcher/tests Console_TestListener.php Dispatcher_testcase.php test.php |
||
| Groups: | php.pear.cvs | ||
| Request: | Send a blank email to pear-cvs+get-29146@lists.php.net to get a copy of this message | ||
mansion Sat Feb 5 08:10:38 2005 EDT
Added files:
/pear/Event_Dispatcher Dispatcher.php Notification.php package.xml
/pear/Event_Dispatcher/examples bubbling.php cancel.php
notification-class.php object.php
/pear/Event_Dispatcher/tests Console_TestListener.php
Dispatcher_testcase.php test.php
Log:
Adding Event_Dispatcher
http://cvs.php.net/co.php/pear/Event_Dispatcher/Dispatcher.php?r=1.1&p=1 Index: pear/Event_Dispatcher/Dispatcher.php +++ pear/Event_Dispatcher/Dispatcher.php <?php // +-----------------------------------------------------------------------+ // | Copyright (c) 2005, Bertrand Mansion | // | All rights reserved. | // | | // | Redistribution and use in source and binary forms, with or without | // | modification, are permitted provided that the following conditions | // | are met: | // | | // | o Redistributions of source code must retain the above copyright | // | notice, this list of conditions and the following disclaimer. | // | o Redistributions in binary form must reproduce the above copyright | // | notice, this list of conditions and the following disclaimer in the | // | documentation and/or other materials provided with the distribution.| // | o The names of the authors may not be used to endorse or promote | // | products derived from this software without specific prior written | // | permission. | // | | // | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | // | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | // | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | // | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | // | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | // | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | // | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | // | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | // | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | // | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | // | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | // | | // +-----------------------------------------------------------------------+ // | Author: Bertrand Mansion <bmansion@mamasam.com> | // | Stephan Schmidt <schst@php.net> | // +-----------------------------------------------------------------------+ // // $Id: Dispatcher.php,v 1.1 2005/02/05 13:10:37 mansion Exp $ require_once 'Event/Notification.php'; /** * 'static property' for Notification object */ $GLOBALS['_Event_Dispatcher'] = array( 'NotificationClass' => 'Event_Notification' ); /** * Registers a global observer */ define('EVENT_DISPATCHER_GLOBAL', ''); /** * Dispatch notifications using PHP callbacks * * The Event_Dispatcher acts acts as a notification dispatch table. * It is used to notify other objects of interesting things, if * they meet certain criteria. This information is encapsulated * in {@link Event_Notification} objects. Client objects register * themselves with the Event_Dispatcher as observers of specific * notifications posted by other objects. When an event occurs, * an object posts an appropriate notification to the Event_Dispatcher. * The Event_Dispatcher dispatches a message to each * registered observer, passing the notification as the sole argument. * * The Event_Dispatcher is actually a combination of three design * patterns: the Singleton, {@link http://c2.com/cgi/wiki?MediatorPattern Mediator}, * and Observer patterns. The idea behind Event_Dispatcher is borrowed from * {@link http://developer.apple.com/documentation/Cocoa/Conceptual/Notifications/index.html Apple's Cocoa framework}. * * @author Bertrand Mansion <bmansion@mamasam.com> * @author Stephan Schmidt <schst@php.net> * @copyright 2005 * @license http://www.opensource.org/licenses/bsd-license.php BSD License * @version @VER@ * @package Event_Dispatcher */ class Event_Dispatcher { /** * Registered observer callbacks * @var array * @access private */ var $_ro = array(); /** * Pending notifications * @var array * @access private */ var $_pending = array(); /** * Nested observers * @var array * @access private */ var $_nestedDispatchers = array(); /** * Name of the dispatcher * @var string * @access private */ var $_name = null; /** * Class used for notifications * @var string * @access private */ var $_notificationClass = null; /** * PHP4 constructor * * Please use {@link getInstance()} instead. * * @access private * @param string Name of the notification dispatcher. */ function Event_Dispatcher($name) { Event_Dispatcher::__construct($name); } /** * PHP5 constructor * * Please use {@link getInstance()} instead. * * @access private * @param string Name of the notification dispatcher. */ function __construct($name) { $this->_name = $name; $this->_notificationClass = $GLOBALS['_Event_Dispatcher']['NotificationClass']; } /** * Returns a notification dispatcher singleton * * There is usually no need to have more than one notification * center for an application so this is the recommended way * to get a Event_Dispatcher object. * * @param string Name of the notification dispatcher. * The default notification dispatcher is named __default. * * @return object Event_Dispatcher */ function &getInstance($name = '__default') { static $dispatchers = array(); if (!isset($dispatchers[$name])) { $dispatchers[$name] = new Event_Dispatcher($name); } return $dispatchers[$name]; } /** * Registers an observer callback * * This method registers a {@link http://www.php.net/manual/en/language.pseudo-types.php#language.types.callback callback} * which is called when the notification corresponding to the * criteria given at registration time is posted. * The criteria are the notification name and eventually the * class of the object posted with the notification. * * If there are any pending notifications corresponding to the criteria * given here, the callback will be called straight away. * * If the notification name is empty, the observer will receive all the * posted notifications. Same goes for the class name. * * @access public * @param string Expected notification name, serves as a filter * @param mixed A PHP callback * @param string Expected contained object class, serves as a filter * @return void */ function addObserver($callback, $nName = EVENT_DISPATCHER_GLOBAL, $class = null) { if (is_array($callback)) { if (is_object($callback[0])) { // Note : PHP4 does not allow correct object comparison so // only the class name is used for registration checks. $reg = get_class($callback[0]).'::'.$callback[1]; } else { $reg = $callback[0].'::'.$callback[1]; } } else { $reg = $callback; } $this->_ro[$nName][$reg] = array( 'callback' => $callback, 'class' => $class ); // Post eventual pending notifications for this observer if (isset($this->_pending[$nName])) { foreach (array_keys($this->_pending[$nName]) as $k) { $notification =& $this->_pending[$nName][$k]; if (!$notification->isNotificationCancelled()) { $objClass = get_class($notification->getNotificationObject()); if (empty($class) || strcasecmp($class, $objClass) == 0) { call_user_func_array($callback, array(&$notification)); $notification->increaseNotificationCount(); } } } } } /** * Creates and posts a notification object * * The purpose of the optional associated object is generally to pass * the object posting the notification to the observers, so that the * observers can query the posting object for more information about * the event. * * Notifications are by default added to a pending notification list. * This way, if an observer is not registered by the time they are * posted, it will still be notified when it is added as an observer. * This behaviour can be turned off in order to make sure that only * the registered observers will be notified. * * The info array serves as a container for any kind of useful * information. It is added to the notification object and posted along. * * @access public * @param object Notification associated object * @param string Notification name * @param array Optional user information * @param bool Whether the notification is pending * @param bool Whether you want the notification to bubble up * @return object The notification object */ function &post(&$object, $nName, $info = array(), $pending = true, $bubble = true) { $notification =& new $this->_notificationClass($object, $nName, $info); return $this->postNotification($notification, $pending, $bubble); } /** * Posts the {@link Event_Notification} object * * @access public * @param object The Notification object * @param bool Whether to post the notification immediately * @param bool Whether you want the notification to bubble up * @see Event_Dispatcher::post() * @return object The notification object */ function &postNotification(&$notification, $pending = true, $bubble = true) { $nName = $notification->getNotificationName(); if ($pending === true) { $this->_pending[$nName][] =& $notification; } $objClass = get_class($notification->getNotificationObject()); // Find the registered observers if (isset($this->_ro[$nName])) { foreach (array_keys($this->_ro[$nName]) as $k) { $rObserver =& $this->_ro[$nName][$k]; if ($notification->isNotificationCancelled()) { return $notification; } if (empty($rObserver['class']) || strcasecmp($rObserver['class'], $objClass) == 0) { call_user_func_array($rObserver['callback'], array(&$notification)); $notification->increaseNotificationCount(); } } } // Notify globally registered observers if (isset($this->_ro[EVENT_DISPATCHER_GLOBAL])) { foreach (array_keys($this->_ro[EVENT_DISPATCHER_GLOBAL]) as $k) { $rObserver =& $this->_ro[EVENT_DISPATCHER_GLOBAL][$k]; if ($notification->isNotificationCancelled()) { return $notification; } if (empty($rObserver['class']) || strcasecmp($rObserver['class'], $objClass) == 0) { call_user_func_array($rObserver['callback'], array(&$notification)); $notification->increaseNotificationCount(); } } } if ($bubble === false) { return $notification; } // Notify in nested dispatchers foreach (array_keys($this->_nestedDispatchers) as $nested) { $notification =& $this->_nestedDispatchers[$nested]->postNotification($notification, $pending); } return $notification; } /** * Removes a registered observer that correspond to the given criteria * * @access public * @param mixed A PHP callback * @param string Notification name * @param string Contained object class * @return bool True if an observer was removed, false otherwise */ function removeObserver($callback, $nName = EVENT_DISPATCHER_GLOBAL, $class = null) { if (is_array($callback)) { if (is_object($callback[0])) { $reg = get_class($callback[0]).'::'.$callback[1]; } else { $reg = $callback[0].'::'.$callback[1]; } } else { $reg = $callback; } $removed = false; if (isset($this->_ro[$nName][$reg])) { if (!empty($class)) { if (strcasecmp($this->_ro[$nName][$reg]['class'], $class) == 0) { unset($this->_ro[$nName][$reg]); $removed = true; } } else { unset($this->_ro[$nName][$reg]); $removed = true; } } if (isset($this->_ro[$nName]) && count($this->_ro[$nName]) == 0) { unset($this->_ro[$nName]); } return $removed; } /** * Get the name of the dispatcher. * * The name is the unique identifier of a dispatcher. * * @access public * @return string name of the dispatcher */ function getName() { return $this->_name; } /** * add a new nested dispatcher * * Notifications will be broadcasted to this dispatcher as well, which * allows you to create event bubbling. * * @access public * @param Event_Dispatcher The nested dispatcher */ function addNestedDispatcher(&$dispatcher) { $name = $dispatcher->getName(); $this->_nestedDispatchers[$name] =& $dispatcher; } /** * remove a nested dispatcher * * @access public * @param Event_Dispatcher Dispatcher to remove * @return boolean */ function removeNestedDispatcher($dispatcher) { if (is_object($dispatcher)) { $dispatcher = $dispatcher->getName(); } if (!isset($this->_nestedDispatchers[$dispatcher])) { return false; } unset($this->_nestedDispatchers[$dispatcher]); return true; } /** * Changes the class used for notifications * * You may call this method on an object to change it for a single * dispatcher or statically, to set the default for all dispatchers * that will be created. * * @access public * @param string name of the notification class * @return boolean */ function setNotificationClass($class) { if (isset($this) && is_a($this, 'Event_Dispatcher')) { $this->_notificationClass = $class; return true; } $GLOBALS['_Event_Dispatcher']['NotificationClass'] = $class; return true; } } ?> http://cvs.php.net/co.php/pear/Event_Dispatcher/Notification.php?r=1.1&p=1 Index: pear/Event_Dispatcher/Notification.php +++ pear/Event_Dispatcher/Notification.php <?php // +-----------------------------------------------------------------------+ // | Copyright (c) 2005, Bertrand Mansion | // | All rights reserved. | // | | // | Redistribution and use in source and binary forms, with or without | // | modification, are permitted provided that the following conditions | // | are met: | // | | // | o Redistributions of source code must retain the above copyright | // | notice, this list of conditions and the following disclaimer. | // | o Redistributions in binary form must reproduce the above copyright | // | notice, this list of conditions and the following disclaimer in the | // | documentation and/or other materials provided with the distribution.| // | o The names of the authors may not be used to endorse or promote | // | products derived from this software without specific prior written | // | permission. | // | | // | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | // | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | // | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | // | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | // | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | // | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | // | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | // | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | // | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | // | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | // | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | // | | // +-----------------------------------------------------------------------+ // | Author: Bertrand Mansion <bmansion@mamasam.com> | // | Stephan Schmidt <schst@php.net> | // +-----------------------------------------------------------------------+ // // $Id: Notification.php,v 1.1 2005/02/05 13:10:37 mansion Exp $ /** * Default state of the notification */ define('EVENT_NOTIFICATION_STATE_DEFAULT', 0); /** * Notification has been cancelled */ define('EVENT_NOTIFICATION_STATE_CANCELLED', 1); /** * A Notification object * * The Notification object can be easily subclassed and serves as a container * for the information about the notification. It holds an object which is * usually a reference to the object that posted the notification, * a notification name used to identify the notification and some user * information which can be anything you need. * * @author Bertrand Mansion <bmansion@mamasam.com> * @author Stephan Schmidt <schst@php.net> * @copyright 2005 * @license http://www.opensource.org/licenses/bsd-license.php BSD License * @version @VER@ * @package Event_Dispatcher */ class Event_Notification { /** * name of the notofication * @var string * @access private */ var $_notificationName; /** * object of interesed (the sender of the notification, in most cases) * @var object * @access private */ var $_notificationObject; /** * additional information about the notification * @var mixed * @access private */ var $_notificationInfo = array(); /** * state of the notification * * This may be: * - EVENT_NOTIFICATION_STATE_DEFAULT * - EVENT_NOTIFICATION_STATE_CANCELLED * * @var integer * @access private */ var $_notificationState = EVENT_NOTIFICATION_STATE_DEFAULT; /** * amount of observers that received this notification * @var mixed * @access private */ var $_notificationCount = 0; /** * Constructor * * @access public * @param object The object of interest for the notification, usually is the posting object * @param string Notification name * @param array Free information array */ function Event_Notification(&$object, $name, $info = array()) { $this->_notificationObject =& $object; $this->_notificationName = $name; $this->_notificationInfo = $info; } /** * Returns the notification name * @return string Notification name */ function getNotificationName() { return $this->_notificationName; } /** * Returns the contained object * @return object Contained object */ function &getNotificationObject() { return $this->_notificationObject; } /** * Returns the user info array * @return array user info */ function getNotificationInfo() { return $this->_notificationInfo; } /** * Increase the internal count * * @access public */ function increaseNotificationCount() { ++$this->_notificationCount; } /** * Get the number of posted notifications * * @access public * @return int */ function getNotificationCount() { return $this->_notificationCount; } /** * Cancel the notification * * @access public * @return void */ function cancelNotification() { $this->_notificationState = EVENT_NOTIFICATION_STATE_CANCELLED; } /** * Checks whether the notification has been cancelled * * @access public * @return boolean */ function isNotificationCancelled() { return ($this->_notificationState === EVENT_NOTIFICATION_STATE_CANCELLED); } } ?> http://cvs.php.net/co.php/pear/Event_Dispatcher/package.xml?r=1.1&p=1 Index: pear/Event_Dispatcher/package.xml +++ pear/Event_Dispatcher/package.xml <?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE package SYSTEM "http://pear.php.net/dtd/package-1.0"> <package version="1.0"> <name>Event_Dispatcher</name> <summary>Dispatch notifications using PHP callbacks</summary> <description> The Event_Dispatcher acts as a notification dispatch table. It is used to notify other objects of interesting things. This information is encapsulated in Event_Notification objects. Client objects register themselves with the Event_Dispatcher as observers of specific notifications posted by other objects. When an event occurs, an object posts an appropriate notification to the Event_Dispatcher. The Event_Dispatcher dispatches a message to each registered observer, passing the notification as the sole argument. </description> <license>PHP License</license> <maintainers> <maintainer> <user>mansion</user> <role>lead</role> <name>Bertrand Mansion</name> <email>bmansion@mamasam.com</email> </maintainer> <maintainer> <user>schst</user> <role>developer</role> <name>Stephan Schmidt</name> <email>schst@php.net</email> </maintainer> </maintainers> <release> <version>0.9.1</version> <date>2005-02-05</date> <license>PHP License</license> <state>beta</state> <notes><![CDATA[ First release ]]></notes> <filelist> <dir name="/" baseinstalldir="Event"> <file role="php" name="Dispatcher.php"/> <file role="php" name="Notification.php"/> <dir name="tests"> <file role="test" name="Console_TestListener.php"/> <file role="test" name="Dispatcher_testcase.php"/> <file role="test" name="test.php"/> </dir> <dir name="examples"> <file role="doc" name="bubbling.php"/> <file role="doc" name="cancel.php"/> <file role="doc" name="notification-class.php"/> <file role="doc" name="object.php"/> </dir> </dir> </filelist> </release> </package> http://cvs.php.net/co.php/pear/Event_Dispatcher/examples/bubbling.php?r=1.1&p=1 Index: pear/Event_Dispatcher/examples/bubbling.php +++ pear/Event_Dispatcher/examples/bubbling.php <?PHP /** * example that shows how to create event bubbling * * This allows you to create several levels of event handling and you * may post a notification to any of these levels. * * After a notification has been posted on a lower level, it will bubble * up through all other levels. * * @package Event_Dispatcher * @subpackage Examples * @author Stephan Schmidt <schst@php.net> */ /** * load Event_Dispatcher package */ require_once 'Event/Dispatcher.php'; /** * example sender class */ class sender { var $_dispatcher = null; function sender(&$dispatcher) { $this->_dispatcher = &$dispatcher; } function foo($bubble = true) { $this->_dispatcher->post($this, 'onFoo', 'Some Info...', true, $bubble); } } /** * example observer */ function receiver1(&$notification) { echo "receiver 1 received notification<br />\n"; } /** * example observer */ function receiver2(&$notification) { echo "receiver 2 received notification<br />\n"; } /** * example observer */ function receiver3(&$notification) { echo "receiver 3 received notification<br />\n"; } // get the different dispatchers $dispatcher1 = &Event_Dispatcher::getInstance(); $dispatcher2 = &Event_Dispatcher::getInstance('child'); $dispatcher3 = &Event_Dispatcher::getInstance('grandchild'); // create senders in two different levels $sender1 = &new sender($dispatcher1); $sender2 = &new sender($dispatcher2); // build three levels $dispatcher1->addNestedDispatcher($dispatcher2); $dispatcher2->addNestedDispatcher($dispatcher3); // add observers in level one and two $dispatcher1->addObserver('receiver1', 'onFoo'); $dispatcher2->addObserver('receiver2', 'onFoo'); // this will bubble up from 1 to 3 echo 'sender1->foo()<br />'; $sender1->foo(); // this will not bubble up echo '<br />'; echo 'sender1->foo(), but disable bubbling<br />'; $sender1->foo(false); // this will bubble up from 2 to 3 echo '<br />'; echo 'sender2->foo()<br />'; $sender2->foo(); // This observer will receive the two pending notifications on level 3 echo '<br />'; echo 'dispatcher3->addObserver()<br />'; $dispatcher3->addObserver('receiver3', 'onFoo'); // remove one level $success = $dispatcher1->removeNestedDispatcher($dispatcher2); if ($success === true) { echo '<br />'; echo 'removed nested dispatcher2 from dispatcher1<br />'; } // this will stay in level 1 echo 'sender1->foo()<br />'; $sender1->foo(); // this will bubble up from 2-3 echo '<br />'; echo 'sender2->foo()<br />'; $sender2->foo(); ?> http://cvs.php.net/co.php/pear/Event_Dispatcher/examples/cancel.php?r=1.1&p=1 Index: pear/Event_Dispatcher/examples/cancel.php +++ pear/Event_Dispatcher/examples/cancel.php <?PHP /** * example that shows how to cancel an event * * @package Event_Dispatcher * @subpackage Examples * @author Stephan Schmidt <schst@php.net> */ /** * load Event_Dispatcher package */ require_once 'Event/Dispatcher.php'; /** * example sender */ class sender { var $_dispatcher = null; function sender(&$dispatcher) { $this->_dispatcher = &$dispatcher; } function foo() { $this->_dispatcher->post($this, 'onFoo', 'Some Info...'); } } /** * example observer */ function receiver1(&$notification) { echo "receiver 1 received notification<br />\n"; // the notification will be cancelled and no other observers // will be notified $notification->cancelNotification(); } /** * example observer */ function receiver2(&$notification) { echo "receiver 2 received notification<br />\n"; } $dispatcher = &Event_Dispatcher::getInstance(); $sender = &new sender($dispatcher); $dispatcher->addObserver('receiver1', 'onFoo'); $dispatcher->addObserver('receiver2', 'onFoo'); $sender->foo(); ?> http://cvs.php.net/co.php/pear/Event_Dispatcher/examples/notification-class.php?r=1.1&p=1 Index: pear/Event_Dispatcher/examples/notification-class.php +++ pear/Event_Dispatcher/examples/notification-class.php <?PHP /** * example that shows how to change the class used for notifications * * @package Event_Dispatcher * @subpackage Examples * @author Stephan Schmidt <schst@php.net> */ /** * load Event_Dispatcher package */ require_once 'Event/Dispatcher.php'; /** * example sender */ class sender { var $_dispatcher = null; function sender(&$dispatcher) { $this->_dispatcher = &$dispatcher; } function foo() { $this->_dispatcher->post($this, 'onFoo', 'Some Info...'); } } function receiver(&$notification) { echo 'received notification: '; echo get_class($notification); echo '<br />'; } class MyNotification extends Event_Notification { } $dispatcher = &Event_Dispatcher::getInstance(); $dispatcher->setNotificationClass('MyNotification'); $sender = &new sender($dispatcher); $dispatcher->addObserver('receiver'); echo 'sender->foo()<br />'; $sender->foo(); Event_Dispatcher::setNotificationClass('MyNotification'); $dispatcher2 = &Event_Dispatcher::getInstance(); $sender2 = &new sender($dispatcher2); $dispatcher2->addObserver('receiver'); echo '<br />sender2->foo()<br />'; $sender2->foo(); ?> http://cvs.php.net/co.php/pear/Event_Dispatcher/examples/object.php?r=1.1&p=1 Index: pear/Event_Dispatcher/examples/object.php +++ pear/Event_Dispatcher/examples/object.php <?PHP /** * example that show how to use objects as observers without * loosing references * * @package Event_Dispatcher * @subpackage Examples * @author Stephan Schmidt <schst@php.net> */ /** * load Event_Dispatcher package */ require_once 'Event/Dispatcher.php'; /** * example sender */ class sender { var $_dispatcher = null; function sender(&$dispatcher) { $this->_dispatcher = &$dispatcher; } function foo() { $notification = &$this->_dispatcher->post($this, 'onFoo', 'Some Info...'); echo "notification::foo is {$notification->foo}<br />"; } } /** * example observer */ class receiver { var $foo; function notify(&$notification) { echo "received notification<br />"; echo "receiver::foo is {$this->foo}<br />"; $notification->foo = 'bar'; } } $dispatcher = &Event_Dispatcher::getInstance(); $sender = &new sender($dispatcher); $receiver = new receiver(); $receiver->foo = 42; // make sure you are using an ampersand here! $dispatcher->addObserver(array(&$receiver, 'notify')); $receiver->foo = 'bar'; echo 'sender->foo()<br />'; $sender->foo(); ?> http://cvs.php.net/co.php/pear/Event_Dispatcher/tests/Console_TestListener.php?r=1.1&p=1 Index: pear/Event_Dispatcher/tests/Console_TestListener.php +++ pear/Event_Dispatcher/tests/Console_TestListener.php <?php class Console_TestListener extends PHPUnit_TestListener { function addError(&$test, &$t) { $this->_errors += 1; echo " Error $this->_errors in " . $test->getName() . " : $t\n"; } function addFailure(&$test, &$t) { $this->_fails += 1; if ($this->_fails == 1) { echo "\n"; } echo "Failure $this->_fails : $t\n"; } function endTest(&$test) { if ($this->_fails == 0 && $this->_errors == 0) { echo ' Test passed'; } else { echo "There were $this->_fails failures for " . $test->getName() . "\n"; echo "There were $this->_errors errors for " . $test->getName() . "\n"; } echo "\n"; } function startTest(&$test) { $this->_fails = 0; $this->_errors = 0; echo get_class($test) . " : Starting " . $test->getName() . " ..."; } } ?> http://cvs.php.net/co.php/pear/Event_Dispatcher/tests/Dispatcher_testcase.php?r=1.1&p=1 Index: pear/Event_Dispatcher/tests/Dispatcher_testcase.php +++ pear/Event_Dispatcher/tests/Dispatcher_testcase.php <?php // $Id: Dispatcher_testcase.php,v 1.1 2005/02/05 13:10:38 mansion Exp $ /** * Unit tests for Event_Dispatcher package. * * @author Bertrand Mansion <bmansion@mamasam.com> */ class Notified { var $notif; function notifReceived(&$notif) { $this->notif =& $notif; } function description() { $notObj =& $this->notif->getNotificationObject(); $name = $this->notif->getNotificationName(); $info = $this->notif->getNotificationInfo(); $desc = $name.':'.implode(':', $info).':'.$notObj->id; return $desc; } } class Dummy { var $id; function Dummy($id = 'default') { $this->id = $id; } } class Notifier { var $id = 'notifier'; function Notifier($id) { $this->id = $id; $ed =& Event_Dispatcher::getInstance(); $ed->post($this, 'NotifierInstanciated', array('info')); } } function notified(&$notif) { $obj = $notif->getNotificationObject(); $obj->id = $notif->getNotificationName().':'.implode(':', $notif->getNotificationInfo()); } class Dispatcher_testCase extends PHPUnit_TestCase { function Dispatcher_testCase($name) { $this->PHPUnit_TestCase($name); } // Get the default dispatch center function test1() { $nf = new Notified(); $dm = new Dummy(); $ed =& Event_Dispatcher::getInstance(); // Generic notification, global observer $ed->addObserver(array(&$nf, 'notifReceived')); $not =& $ed->post($dm, 'test', array('A', 'B')); $this->assertEquals('test:A:B:default', $nf->description(), "Error"); $this->assertEquals(1, $not->getNotificationCount(), "Wrong notification count"); // Object references $dm->id = 'dummy'; $this->assertEquals('test:A:B:dummy', $nf->description(), "Wrong notification description"); // Named notifications $ed->addObserver('notified', 'NotifierInstanciated'); $nt = new Notifier('notifier'); $this->assertEquals('NotifierInstanciated:info', $nt->id, "Wrong notification id"); // Pending notifications $not =& $ed->post($nt, 'PendingNotification'); $ed->addObserver(array(&$nf, 'notifReceived'), 'PendingNotification'); $this->assertEquals('PendingNotification::NotifierInstanciated:info', $nf->description(), "Error"); $this->assertEquals(2, $not->getNotificationCount(), "Error"); // Class filter 1 $ed->addObserver(array(&$nf, 'notifReceived'), 'ClassFilterNotification', 'Dummy'); $not =& $ed->post($nt, 'ClassFilterNotification', array('isGlobal')); $this->assertEquals('ClassFilterNotification:isGlobal:NotifierInstanciated:info', $nf->description(), "Error"); $this->assertEquals(1, $not->getNotificationCount(), "Error"); // Remove observer $ed->removeObserver(array(&$nf, 'notifReceived')); $nt->id = 'reset'; $not =& $ed->post($nt, 'ClassFilterNotification', array('test')); $this->assertEquals('ClassFilterNotification:isGlobal:reset', $nf->description(), "Error"); $this->assertEquals(0, $not->getNotificationCount(), "Error"); // Class filter 2 $not =& $ed->post($dm, 'ClassFilterNotification'); $this->assertEquals('ClassFilterNotification::dummy', $nf->description(), "Error"); $this->assertEquals(1, $not->getNotificationCount(), "Error"); // Re-add the global observer $ed->addObserver(array(&$nf, 'notifReceived')); $not =& $ed->post($dm, 'ClassFilterNotification'); $this->assertEquals('ClassFilterNotification::dummy', $nf->description(), "Error"); $this->assertEquals(2, $not->getNotificationCount(), "Error"); } // Tests with 2 dispatchers function test2() { $nf = new Notified(); $dm = new Dummy(); $ed2 =& Event_Dispatcher::getInstance('another'); $ed1 =& Event_Dispatcher::getInstance(); $ed2->addObserver(array(&$nf, 'notifReceived')); $not =& $ed2->post($dm, 'test', array('A', 'B')); $this->assertEquals('test:A:B:default', $nf->description(), "Error"); $this->assertEquals(1, $not->getNotificationCount(), "Error"); $not =& $ed1->post($dm, 'test', array('A2', 'B2')); $this->assertEquals(1, $not->getNotificationCount(), "Error"); $not =& $ed1->post($dm, 'test', array('A2', 'B2')); $this->assertEquals(1, $not->getNotificationCount(), "Error"); $ed2->addObserver(array(&$nf, 'notifReceived'), 'ClassFilterNotification', 'Notifier'); $not =& $ed2->post($dm, 'ClassFilterNotification'); $this->assertEquals('ClassFilterNotification::default', $nf->description(), "Error"); $this->assertEquals(1, $not->getNotificationCount(), "Error"); $ed2->addObserver(array(&$nf, 'notifReceived'), 'ClassFilterNotification', 'Dummy'); $not =& $ed2->post($dm, 'ClassFilterNotification'); $this->assertEquals(2, $not->getNotificationCount(), "Error"); } } ?> http://cvs.php.net/co.php/pear/Event_Dispatcher/tests/test.php?r=1.1&p=1 Index: pear/Event_Dispatcher/tests/test.php +++ pear/Event_Dispatcher/tests/test.php <?php /** * Unit tests for Event_Dispatcher class * * $Id: test.php,v 1.1 2005/02/05 13:10:38 mansion Exp $ */ require_once 'System.php'; require_once 'PHPUnit.php'; require_once 'Event/Dispatcher.php'; $testcases = array( 'Dispatcher_testcase' ); $suite =& new PHPUnit_TestSuite(); foreach ($testcases as $testcase) { include_once $testcase . '.php'; $methods = preg_grep('/^test/i', get_class_methods($testcase)); foreach ($methods as $method) { $suite->addTest(new $testcase($method)); } } require_once './Console_TestListener.php'; $result =& new PHPUnit_TestResult(); $result->addListener(new Console_TestListener); $suite->run($result); ?>
http://cvs.php.net/co.php/pear/Event_Dispatcher/Dispatcher.php?r=1.1&p=1 Index: pear/Event_Dispatcher/Dispatcher.php +++ pear/Event_Dispatcher/Dispatcher.php <?php // +-----------------------------------------------------------------------+ // | Copyright (c) 2005, Bertrand Mansion | // | All rights reserved. | // | | // | Redistribution and use in source and binary forms, with or without | // | modification, are permitted provided that the following conditions | // | are met: | // | | // | o Redistributions of source code must retain the above copyright | // | notice, this list of conditions and the following disclaimer. | // | o Redistributions in binary form must reproduce the above copyright | // | notice, this list of conditions and the following disclaimer in the | // | documentation and/or other materials provided with the distribution.| // | o The names of the authors may not be used to endorse or promote | // | products derived from this software without specific prior written | // | permission. | // | | // | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | // | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | // | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | // | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | // | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | // | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | // | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | // | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | // | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | // | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | // | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | // | | // +-----------------------------------------------------------------------+ // | Author: Bertrand Mansion <bmansion@mamasam.com> | // | Stephan Schmidt <schst@php.net> | // +-----------------------------------------------------------------------+ // // $Id: Dispatcher.php,v 1.1 2005/02/05 13:10:37 mansion Exp $ require_once 'Event/Notification.php'; /** * 'static property' for Notification object */ $GLOBALS['_Event_Dispatcher'] = array( 'NotificationClass' => 'Event_Notification' ); /** * Registers a global observer */ define('EVENT_DISPATCHER_GLOBAL', ''); /** * Dispatch notifications using PHP callbacks * * The Event_Dispatcher acts acts as a notification dispatch table. * It is used to notify other objects of interesting things, if * they meet certain criteria. This information is encapsulated * in {@link Event_Notification} objects. Client objects register * themselves with the Event_Dispatcher as observers of specific * notifications posted by other objects. When an event occurs, * an object posts an appropriate notification to the Event_Dispatcher. * The Event_Dispatcher dispatches a message to each * registered observer, passing the notification as the sole argument. * * The Event_Dispatcher is actually a combination of three design * patterns: the Singleton, {@link http://c2.com/cgi/wiki?MediatorPattern Mediator}, * and Observer patterns. The idea behind Event_Dispatcher is borrowed from * {@link http://developer.apple.com/documentation/Cocoa/Conceptual/Notifications/index.html Apple's Cocoa framework}. * * @author Bertrand Mansion <bmansion@mamasam.com> * @author Stephan Schmidt <schst@php.net> * @copyright 2005 * @license http://www.opensource.org/licenses/bsd-license.php BSD License * @version @VER@ * @package Event_Dispatcher */ class Event_Dispatcher { /** * Registered observer callbacks * @var array * @access private */ var $_ro = array(); /** * Pending notifications * @var array * @access private */ var $_pending = array(); /** * Nested observers * @var array * @access private */ var $_nestedDispatchers = array(); /** * Name of the dispatcher * @var string * @access private */ var $_name = null; /** * Class used for notifications * @var string * @access private */ var $_notificationClass = null; /** * PHP4 constructor * * Please use {@link getInstance()} instead. * * @access private * @param string Name of the notification dispatcher. */ function Event_Dispatcher($name) { Event_Dispatcher::__construct($name); } /** * PHP5 constructor * * Please use {@link getInstance()} instead. * * @access private * @param string Name of the notification dispatcher. */ function __construct($name) { $this->_name = $name; $this->_notificationClass = $GLOBALS['_Event_Dispatcher']['NotificationClass']; } /** * Returns a notification dispatcher singleton * * There is usually no need to have more than one notification * center for an application so this is the recommended way * to get a Event_Dispatcher object. * * @param string Name of the notification dispatcher. * The default notification dispatcher is named __default. * * @return object Event_Dispatcher */ function &getInstance($name = '__default') { static $dispatchers = array(); if (!isset($dispatchers[$name])) { $dispatchers[$name] = new Event_Dispatcher($name); } return $dispatchers[$name]; } /** * Registers an observer callback * * This method registers a {@link http://www.php.net/manual/en/language.pseudo-types.php#language.types.callback callback} * which is called when the notification corresponding to the * criteria given at registration time is posted. * The criteria are the notification name and eventually the * class of the object posted with the notification. * * If there are any pending notifications corresponding to the criteria * given here, the callback will be called straight away. * * If the notification name is empty, the observer will receive all the * posted notifications. Same goes for the class name. * * @access public * @param string Expected notification name, serves as a filter * @param mixed A PHP callback * @param string Expected contained object class, serves as a filter * @return void */ function addObserver($callback, $nName = EVENT_DISPATCHER_GLOBAL, $class = null) { if (is_array($callback)) { if (is_object($callback[0])) { // Note : PHP4 does not allow correct object comparison so // only the class name is used for registration checks. $reg = get_class($callback[0]).'::'.$callback[1]; } else { $reg = $callback[0].'::'.$callback[1]; } } else { $reg = $callback; } $this->_ro[$nName][$reg] = array( 'callback' => $callback, 'class' => $class ); // Post eventual pending notifications for this observer if (isset($this->_pending[$nName])) { foreach (array_keys($this->_pending[$nName]) as $k) { $notification =& $this->_pending[$nName][$k]; if (!$notification->isNotificationCancelled()) { $objClass = get_class($notification->getNotificationObject()); if (empty($class) || strcasecmp($class, $objClass) == 0) { call_user_func_array($callback, array(&$notification)); $notification->increaseNotificationCount(); } } } } } /** * Creates and posts a notification object * * The purpose of the optional associated object is generally to pass * the object posting the notification to the observers, so that the * observers can query the posting object for more information about * the event. * * Notifications are by default added to a pending notification list. * This way, if an observer is not registered by the time they are * posted, it will still be notified when it is added as an observer. * This behaviour can be turned off in order to make sure that only * the registered observers will be notified. * * The info array serves as a container for any kind of useful * information. It is added to the notification object and posted along. * * @access public * @param object Notification associated object * @param string Notification name * @param array Optional user information * @param bool Whether the notification is pending * @param bool Whether you want the notification to bubble up * @return object The notification object */ function &post(&$object, $nName, $info = array(), $pending = true, $bubble = true) { $notification =& new $this->_notificationClass($object, $nName, $info); return $this->postNotification($notification, $pending, $bubble); } /** * Posts the {@link Event_Notification} object * * @access public * @param object The Notification object * @param bool Whether to post the notification immediately * @param bool Whether you want the notification to bubble up * @see Event_Dispatcher::post() * @return object The notification object */ function &postNotification(&$notification, $pending = true, $bubble = true) { $nName = $notification->getNotificationName(); if ($pending === true) { $this->_pending[$nName][] =& $notification; } $objClass = get_class($notification->getNotificationObject()); // Find the registered observers if (isset($this->_ro[$nName])) { foreach (array_keys($this->_ro[$nName]) as $k) { $rObserver =& $this->_ro[$nName][$k]; if ($notification->isNotificationCancelled()) { return $notification; } if (empty($rObserver['class']) || strcasecmp($rObserver['class'], $objClass) == 0) { call_user_func_array($rObserver['callback'], array(&$notification)); $notification->increaseNotificationCount(); } } } // Notify globally registered observers if (isset($this->_ro[EVENT_DISPATCHER_GLOBAL])) { foreach (array_keys($this->_ro[EVENT_DISPATCHER_GLOBAL]) as $k) { $rObserver =& $this->_ro[EVENT_DISPATCHER_GLOBAL][$k]; if ($notification->isNotificationCancelled()) { return $notification; } if (empty($rObserver['class']) || strcasecmp($rObserver['class'], $objClass) == 0) { call_user_func_array($rObserver['callback'], array(&$notification)); $notification->increaseNotificationCount(); } } } if ($bubble === false) { return $notification; } // Notify in nested dispatchers foreach (array_keys($this->_nestedDispatchers) as $nested) { $notification =& $this->_nestedDispatchers[$nested]->postNotification($notification, $pending); } return $notification; } /** * Removes a registered observer that correspond to the given criteria * * @access public * @param mixed A PHP callback * @param string Notification name * @param string Contained object class * @return bool True if an observer was removed, false otherwise */ function removeObserver($callback, $nName = EVENT_DISPATCHER_GLOBAL, $class = null) { if (is_array($callback)) { if (is_object($callback[0])) { $reg = get_class($callback[0]).'::'.$callback[1]; } else { $reg = $callback[0].'::'.$callback[1]; } } else { $reg = $callback; } $removed = false; if (isset($this->_ro[$nName][$reg])) { if (!empty($class)) { if (strcasecmp($this->_ro[$nName][$reg]['class'], $class) == 0) { unset($this->_ro[$nName][$reg]); $removed = true; } } else { unset($this->_ro[$nName][$reg]); $removed = true; } } if (isset($this->_ro[$nName]) && count($this->_ro[$nName]) == 0) { unset($this->_ro[$nName]); } return $removed; } /** * Get the name of the dispatcher. * * The name is the unique identifier of a dispatcher. * * @access public * @return string name of the dispatcher */ function getName() { return $this->_name; } /** * add a new nested dispatcher * * Notifications will be broadcasted to this dispatcher as well, which * allows you to create event bubbling. * * @access public * @param Event_Dispatcher The nested dispatcher */ function addNestedDispatcher(&$dispatcher) { $name = $dispatcher->getName(); $this->_nestedDispatchers[$name] =& $dispatcher; } /** * remove a nested dispatcher * * @access public * @param Event_Dispatcher Dispatcher to remove * @return boolean */ function removeNestedDispatcher($dispatcher) { if (is_object($dispatcher)) { $dispatcher = $dispatcher->getName(); } if (!isset($this->_nestedDispatchers[$dispatcher])) { return false; } unset($this->_nestedDispatchers[$dispatcher]); return true; } /** * Changes the class used for notifications * * You may call this method on an object to change it for a single * dispatcher or statically, to set the default for all dispatchers * that will be created. * * @access public * @param string name of the notification class * @return boolean */ function setNotificationClass($class) { if (isset($this) && is_a($this, 'Event_Dispatcher')) { $this->_notificationClass = $class; return true; } $GLOBALS['_Event_Dispatcher']['NotificationClass'] = $class; return true; } } ?> http://cvs.php.net/co.php/pear/Event_Dispatcher/Notification.php?r=1.1&p=1 Index: pear/Event_Dispatcher/Notification.php +++ pear/Event_Dispatcher/Notification.php <?php // +-----------------------------------------------------------------------+ // | Copyright (c) 2005, Bertrand Mansion | // | All rights reserved. | // | | // | Redistribution and use in source and binary forms, with or without | // | modification, are permitted provided that the following conditions | // | are met: | // | | // | o Redistributions of source code must retain the above copyright | // | notice, this list of conditions and the following disclaimer. | // | o Redistributions in binary form must reproduce the above copyright | // | notice, this list of conditions and the following disclaimer in the | // | documentation and/or other materials provided with the distribution.| // | o The names of the authors may not be used to endorse or promote | // | products derived from this software without specific prior written | // | permission. | // | | // | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | // | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | // | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | // | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | // | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | // | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | // | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | // | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | // | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | // | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | // | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | // | | // +-----------------------------------------------------------------------+ // | Author: Bertrand Mansion <bmansion@mamasam.com> | // | Stephan Schmidt <schst@php.net> | // +-----------------------------------------------------------------------+ // // $Id: Notification.php,v 1.1 2005/02/05 13:10:37 mansion Exp $ /** * Default state of the notification */ define('EVENT_NOTIFICATION_STATE_DEFAULT', 0); /** * Notification has been cancelled */ define('EVENT_NOTIFICATION_STATE_CANCELLED', 1); /** * A Notification object * * The Notification object can be easily subclassed and serves as a container * for the information about the notification. It holds an object which is * usually a reference to the object that posted the notification, * a notification name used to identify the notification and some user * information which can be anything you need. * * @author Bertrand Mansion <bmansion@mamasam.com> * @author Stephan Schmidt <schst@php.net> * @copyright 2005 * @license http://www.opensource.org/licenses/bsd-license.php BSD License * @version @VER@ * @package Event_Dispatcher */ class Event_Notification { /** * name of the notofication * @var string * @access private */ var $_notificationName; /** * object of interesed (the sender of the notification, in most cases) * @var object * @access private */ var $_notificationObject; /** * additional information about the notification * @var mixed * @access private */ var $_notificationInfo = array(); /** * state of the notification * * This may be: * - EVENT_NOTIFICATION_STATE_DEFAULT * - EVENT_NOTIFICATION_STATE_CANCELLED * * @var integer * @access private */ var $_notificationState = EVENT_NOTIFICATION_STATE_DEFAULT; /** * amount of observers that received this notification * @var mixed * @access private */ var $_notificationCount = 0; /** * Constructor * * @access public * @param object The object of interest for the notification, usually is the posting object * @param string Notification name * @param array Free information array */ function Event_Notification(&$object, $name, $info = array()) { $this->_notificationObject =& $object; $this->_notificationName = $name; $this->_notificationInfo = $info; } /** * Returns the notification name * @return string Notification name */ function getNotificationName() { return $this->_notificationName; } /** * Returns the contained object * @return object Contained object */ function &getNotificationObject() { return $this->_notificationObject; } /** * Returns the user info array * @return array user info */ function getNotificationInfo() { return $this->_notificationInfo; } /** * Increase the internal count * * @access public */ function increaseNotificationCount() { ++$this->_notificationCount; } /** * Get the number of posted notifications * * @access public * @return int */ function getNotificationCount() { return $this->_notificationCount; } /** * Cancel the notification * * @access public * @return void */ function cancelNotification() { $this->_notificationState = EVENT_NOTIFICATION_STATE_CANCELLED; } /** * Checks whether the notification has been cancelled * * @access public * @return boolean */ function isNotificationCancelled() { return ($this->_notificationState === EVENT_NOTIFICATION_STATE_CANCELLED); } } ?> http://cvs.php.net/co.php/pear/Event_Dispatcher/package.xml?r=1.1&p=1 Index: pear/Event_Dispatcher/package.xml +++ pear/Event_Dispatcher/package.xml <?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE package SYSTEM "http://pear.php.net/dtd/package-1.0"> <package version="1.0"> <name>Event_Dispatcher</name> <summary>Dispatch notifications using PHP callbacks</summary> <description> The Event_Dispatcher acts as a notification dispatch table. It is used to notify other objects of interesting things. This information is encapsulated in Event_Notification objects. Client objects register themselves with the Event_Dispatcher as observers of specific notifications posted by other objects. When an event occurs, an object posts an appropriate notification to the Event_Dispatcher. The Event_Dispatcher dispatches a message to each registered observer, passing the notification as the sole argument. </description> <license>PHP License</license> <maintainers> <maintainer> <user>mansion</user> <role>lead</role> <name>Bertrand Mansion</name> <email>bmansion@mamasam.com</email> </maintainer> <maintainer> <user>schst</user> <role>developer</role> <name>Stephan Schmidt</name> <email>schst@php.net</email> </maintainer> </maintainers> <release> <version>0.9.1</version> <date>2005-02-05</date> <license>PHP License</license> <state>beta</state> <notes><![CDATA[ First release ]]></notes> <filelist> <dir name="/" baseinstalldir="Event"> <file role="php" name="Dispatcher.php"/> <file role="php" name="Notification.php"/> <dir name="tests"> <file role="test" name="Console_TestListener.php"/> <file role="test" name="Dispatcher_testcase.php"/> <file role="test" name="test.php"/> </dir> <dir name="examples"> <file role="doc" name="bubbling.php"/> <file role="doc" name="cancel.php"/> <file role="doc" name="notification-class.php"/> <file role="doc" name="object.php"/> </dir> </dir> </filelist> </release> </package> http://cvs.php.net/co.php/pear/Event_Dispatcher/examples/bubbling.php?r=1.1&p=1 Index: pear/Event_Dispatcher/examples/bubbling.php +++ pear/Event_Dispatcher/examples/bubbling.php <?PHP /** * example that shows how to create event bubbling * * This allows you to create several levels of event handling and you * may post a notification to any of these levels. * * After a notification has been posted on a lower level, it will bubble * up through all other levels. * * @package Event_Dispatcher * @subpackage Examples * @author Stephan Schmidt <schst@php.net> */ /** * load Event_Dispatcher package */ require_once 'Event/Dispatcher.php'; /** * example sender class */ class sender { var $_dispatcher = null; function sender(&$dispatcher) { $this->_dispatcher = &$dispatcher; } function foo($bubble = true) { $this->_dispatcher->post($this, 'onFoo', 'Some Info...', true, $bubble); } } /** * example observer */ function receiver1(&$notification) { echo "receiver 1 received notification<br />\n"; } /** * example observer */ function receiver2(&$notification) { echo "receiver 2 received notification<br />\n"; } /** * example observer */ function receiver3(&$notification) { echo "receiver 3 received notification<br />\n"; } // get the different dispatchers $dispatcher1 = &Event_Dispatcher::getInstance(); $dispatcher2 = &Event_Dispatcher::getInstance('child'); $dispatcher3 = &Event_Dispatcher::getInstance('grandchild'); // create senders in two different levels $sender1 = &new sender($dispatcher1); $sender2 = &new sender($dispatcher2); // build three levels $dispatcher1->addNestedDispatcher($dispatcher2); $dispatcher2->addNestedDispatcher($dispatcher3); // add observers in level one and two $dispatcher1->addObserver('receiver1', 'onFoo'); $dispatcher2->addObserver('receiver2', 'onFoo'); // this will bubble up from 1 to 3 echo 'sender1->foo()<br />'; $sender1->foo(); // this will not bubble up echo '<br />'; echo 'sender1->foo(), but disable bubbling<br />'; $sender1->foo(false); // this will bubble up from 2 to 3 echo '<br />'; echo 'sender2->foo()<br />'; $sender2->foo(); // This observer will receive the two pending notifications on level 3 echo '<br />'; echo 'dispatcher3->addObserver()<br />'; $dispatcher3->addObserver('receiver3', 'onFoo'); // remove one level $success = $dispatcher1->removeNestedDispatcher($dispatcher2); if ($success === true) { echo '<br />'; echo 'removed nested dispatcher2 from dispatcher1<br />'; } // this will stay in level 1 echo 'sender1->foo()<br />'; $sender1->foo(); // this will bubble up from 2-3 echo '<br />'; echo 'sender2->foo()<br />'; $sender2->foo(); ?> http://cvs.php.net/co.php/pear/Event_Dispatcher/examples/cancel.php?r=1.1&p=1 Index: pear/Event_Dispatcher/examples/cancel.php +++ pear/Event_Dispatcher/examples/cancel.php <?PHP /** * example that shows how to cancel an event * * @package Event_Dispatcher * @subpackage Examples * @author Stephan Schmidt <schst@php.net> */ /** * load Event_Dispatcher package */ require_once 'Event/Dispatcher.php'; /** * example sender */ class sender { var $_dispatcher = null; function sender(&$dispatcher) { $this->_dispatcher = &$dispatcher; } function foo() { $this->_dispatcher->post($this, 'onFoo', 'Some Info...'); } } /** * example observer */ function receiver1(&$notification) { echo "receiver 1 received notification<br />\n"; // the notification will be cancelled and no other observers // will be notified $notification->cancelNotification(); } /** * example observer */ function receiver2(&$notification) { echo "receiver 2 received notification<br />\n"; } $dispatcher = &Event_Dispatcher::getInstance(); $sender = &new sender($dispatcher); $dispatcher->addObserver('receiver1', 'onFoo'); $dispatcher->addObserver('receiver2', 'onFoo'); $sender->foo(); ?> http://cvs.php.net/co.php/pear/Event_Dispatcher/examples/notification-class.php?r=1.1&p=1 Index: pear/Event_Dispatcher/examples/notification-class.php +++ pear/Event_Dispatcher/examples/notification-class.php <?PHP /** * example that shows how to change the class used for notifications * * @package Event_Dispatcher * @subpackage Examples * @author Stephan Schmidt <schst@php.net> */ /** * load Event_Dispatcher package */ require_once 'Event/Dispatcher.php'; /** * example sender */ class sender { var $_dispatcher = null; function sender(&$dispatcher) { $this->_dispatcher = &$dispatcher; } function foo() { $this->_dispatcher->post($this, 'onFoo', 'Some Info...'); } } function receiver(&$notification) { echo 'received notification: '; echo get_class($notification); echo '<br />'; } class MyNotification extends Event_Notification { } $dispatcher = &Event_Dispatcher::getInstance(); $dispatcher->setNotificationClass('MyNotification'); $sender = &new sender($dispatcher); $dispatcher->addObserver('receiver'); echo 'sender->foo()<br />'; $sender->foo(); Event_Dispatcher::setNotificationClass('MyNotification'); $dispatcher2 = &Event_Dispatcher::getInstance(); $sender2 = &new sender($dispatcher2); $dispatcher2->addObserver('receiver'); echo '<br />sender2->foo()<br />'; $sender2->foo(); ?> http://cvs.php.net/co.php/pear/Event_Dispatcher/examples/object.php?r=1.1&p=1 Index: pear/Event_Dispatcher/examples/object.php +++ pear/Event_Dispatcher/examples/object.php <?PHP /** * example that show how to use objects as observers without * loosing references * * @package Event_Dispatcher * @subpackage Examples * @author Stephan Schmidt <schst@php.net> */ /** * load Event_Dispatcher package */ require_once 'Event/Dispatcher.php'; /** * example sender */ class sender { var $_dispatcher = null; function sender(&$dispatcher) { $this->_dispatcher = &$dispatcher; } function foo() { $notification = &$this->_dispatcher->post($this, 'onFoo', 'Some Info...'); echo "notification::foo is {$notification->foo}<br />"; } } /** * example observer */ class receiver { var $foo; function notify(&$notification) { echo "received notification<br />"; echo "receiver::foo is {$this->foo}<br />"; $notification->foo = 'bar'; } } $dispatcher = &Event_Dispatcher::getInstance(); $sender = &new sender($dispatcher); $receiver = new receiver(); $receiver->foo = 42; // make sure you are using an ampersand here! $dispatcher->addObserver(array(&$receiver, 'notify')); $receiver->foo = 'bar'; echo 'sender->foo()<br />'; $sender->foo(); ?> http://cvs.php.net/co.php/pear/Event_Dispatcher/tests/Console_TestListener.php?r=1.1&p=1 Index: pear/Event_Dispatcher/tests/Console_TestListener.php +++ pear/Event_Dispatcher/tests/Console_TestListener.php <?php class Console_TestListener extends PHPUnit_TestListener { function addError(&$test, &$t) { $this->_errors += 1; echo " Error $this->_errors in " . $test->getName() . " : $t\n"; } function addFailure(&$test, &$t) { $this->_fails += 1; if ($this->_fails == 1) { echo "\n"; } echo "Failure $this->_fails : $t\n"; } function endTest(&$test) { if ($this->_fails == 0 && $this->_errors == 0) { echo ' Test passed'; } else { echo "There were $this->_fails failures for " . $test->getName() . "\n"; echo "There were $this->_errors errors for " . $test->getName() . "\n"; } echo "\n"; } function startTest(&$test) { $this->_fails = 0; $this->_errors = 0; echo get_class($test) . " : Starting " . $test->getName() . " ..."; } } ?> http://cvs.php.net/co.php/pear/Event_Dispatcher/tests/Dispatcher_testcase.php?r=1.1&p=1 Index: pear/Event_Dispatcher/tests/Dispatcher_testcase.php +++ pear/Event_Dispatcher/tests/Dispatcher_testcase.php <?php // $Id: Dispatcher_testcase.php,v 1.1 2005/02/05 13:10:38 mansion Exp $ /** * Unit tests for Event_Dispatcher package. * * @author Bertrand Mansion <bmansion@mamasam.com> */ class Notified { var $notif; function notifReceived(&$notif) { $this->notif =& $notif; } function description() { $notObj =& $this->notif->getNotificationObject(); $name = $this->notif->getNotificationName(); $info = $this->notif->getNotificationInfo(); $desc = $name.':'.implode(':', $info).':'.$notObj->id; return $desc; } } class Dummy { var $id; function Dummy($id = 'default') { $this->id = $id; } } class Notifier { var $id = 'notifier'; function Notifier($id) { $this->id = $id; $ed =& Event_Dispatcher::getInstance(); $ed->post($this, 'NotifierInstanciated', array('info')); } } function notified(&$notif) { $obj = $notif->getNotificationObject(); $obj->id = $notif->getNotificationName().':'.implode(':', $notif->getNotificationInfo()); } class Dispatcher_testCase extends PHPUnit_TestCase { function Dispatcher_testCase($name) { $this->PHPUnit_TestCase($name); } // Get the default dispatch center function test1() { $nf = new Notified(); $dm = new Dummy(); $ed =& Event_Dispatcher::getInstance(); // Generic notification, global observer $ed->addObserver(array(&$nf, 'notifReceived')); $not =& $ed->post($dm, 'test', array('A', 'B')); $this->assertEquals('test:A:B:default', $nf->description(), "Error"); $this->assertEquals(1, $not->getNotificationCount(), "Wrong notification count"); // Object references $dm->id = 'dummy'; $this->assertEquals('test:A:B:dummy', $nf->description(), "Wrong notification description"); // Named notifications $ed->addObserver('notified', 'NotifierInstanciated'); $nt = new Notifier('notifier'); $this->assertEquals('NotifierInstanciated:info', $nt->id, "Wrong notification id"); // Pending notifications $not =& $ed->post($nt, 'PendingNotification'); $ed->addObserver(array(&$nf, 'notifReceived'), 'PendingNotification'); $this->assertEquals('PendingNotification::NotifierInstanciated:info', $nf->description(), "Error"); $this->assertEquals(2, $not->getNotificationCount(), "Error"); // Class filter 1 $ed->addObserver(array(&$nf, 'notifReceived'), 'ClassFilterNotification', 'Dummy'); $not =& $ed->post($nt, 'ClassFilterNotification', array('isGlobal')); $this->assertEquals('ClassFilterNotification:isGlobal:NotifierInstanciated:info', $nf->description(), "Error"); $this->assertEquals(1, $not->getNotificationCount(), "Error"); // Remove observer $ed->removeObserver(array(&$nf, 'notifReceived')); $nt->id = 'reset'; $not =& $ed->post($nt, 'ClassFilterNotification', array('test')); $this->assertEquals('ClassFilterNotification:isGlobal:reset', $nf->description(), "Error"); $this->assertEquals(0, $not->getNotificationCount(), "Error"); // Class filter 2 $not =& $ed->post($dm, 'ClassFilterNotification'); $this->assertEquals('ClassFilterNotification::dummy', $nf->description(), "Error"); $this->assertEquals(1, $not->getNotificationCount(), "Error"); // Re-add the global observer $ed->addObserver(array(&$nf, 'notifReceived')); $not =& $ed->post($dm, 'ClassFilterNotification'); $this->assertEquals('ClassFilterNotification::dummy', $nf->description(), "Error"); $this->assertEquals(2, $not->getNotificationCount(), "Error"); } // Tests with 2 dispatchers function test2() { $nf = new Notified(); $dm = new Dummy(); $ed2 =& Event_Dispatcher::getInstance('another'); $ed1 =& Event_Dispatcher::getInstance(); $ed2->addObserver(array(&$nf, 'notifReceived')); $not =& $ed2->post($dm, 'test', array('A', 'B')); $this->assertEquals('test:A:B:default', $nf->description(), "Error"); $this->assertEquals(1, $not->getNotificationCount(), "Error"); $not =& $ed1->post($dm, 'test', array('A2', 'B2')); $this->assertEquals(1, $not->getNotificationCount(), "Error"); $not =& $ed1->post($dm, 'test', array('A2', 'B2')); $this->assertEquals(1, $not->getNotificationCount(), "Error"); $ed2->addObserver(array(&$nf, 'notifReceived'), 'ClassFilterNotification', 'Notifier'); $not =& $ed2->post($dm, 'ClassFilterNotification'); $this->assertEquals('ClassFilterNotification::default', $nf->description(), "Error"); $this->assertEquals(1, $not->getNotificationCount(), "Error"); $ed2->addObserver(array(&$nf, 'notifReceived'), 'ClassFilterNotification', 'Dummy'); $not =& $ed2->post($dm, 'ClassFilterNotification'); $this->assertEquals(2, $not->getNotificationCount(), "Error"); } } ?> http://cvs.php.net/co.php/pear/Event_Dispatcher/tests/test.php?r=1.1&p=1 Index: pear/Event_Dispatcher/tests/test.php +++ pear/Event_Dispatcher/tests/test.php <?php /** * Unit tests for Event_Dispatcher class * * $Id: test.php,v 1.1 2005/02/05 13:10:38 mansion Exp $ */ require_once 'System.php'; require_once 'PHPUnit.php'; require_once 'Event/Dispatcher.php'; $testcases = array( 'Dispatcher_testcase' ); $suite =& new PHPUnit_TestSuite(); foreach ($testcases as $testcase) { include_once $testcase . '.php'; $methods = preg_grep('/^test/i', get_class_methods($testcase)); foreach ($methods as $method) { $suite->addTest(new $testcase($method)); } } require_once './Console_TestListener.php'; $result =& new PHPUnit_TestResult(); $result->addListener(new Console_TestListener); $suite->run($result); ?>