-
Notifications
You must be signed in to change notification settings - Fork 0
Docs EventHandling
From time to time it is necessary to notify a specific class about something that happened within another class. A first solution would be to call the class to notify direct from the class where the action happened. This has the drawback that it is not very flexible: every time you want another class to be called you have to touch the class and add the specific class. A first solution to this could be the Observer-Pattern. This will loose the tight coupling of classes, however the classes itself have to implement the Observer and the Subject interface while the Subject still has to notify every single Observer by itself. What we want is a solution that allows us to connect arbitrary classes without the need to change them. The solution is to introduce an EventDispatcher which takes the responsibility of registering listeners for a specific event and notifies them as the event occurs. This concept is derived from Cocoa's Notification Centre.
#php <?php class Auth { ... public function login($userName, $password) { // some authentication code that verifies username and password $this->userName = $userName; $this->isValid = true; $event = stubEventDispatcher::getInstance()->trigger('onLogin', $this); if ($event->isCancelled() == true) { $this->userName = null; $this->isValid = false; } } } class UserLoginLogging implements stubEventListener { public function handleEvent(Event $event) { $auth = $event->getContext(); error_log(date('Y-m-d H:i:s', $event->getTime()) . '|' . $event->getName() . '|' . $auth->getUserName(), 3, 'user.log'); } } class BlackList implements stubEventListener { ... public function handleEvent(Event $event) { $auth = $event->getContext(); if ($this->isBlackListed($auth->getUserName()) == true) { $event->cancel(); error_log(date('Y-m-d H:i:s', $event->getTime()) . '|cancelledLogin|' . $auth->getUserName(), 3, 'user.log'); } } } $eventDispatcher = stubEventDispatcher::getInstance(); $eventDispatcher->register(new BlackList(), 'onLogin'); $eventDispatcher->register(new UserLoginLogging(), 'onLogin'); $auth = new Auth(); $auth->login('mikey'); var_dump($auth->isValid()); $auth->login('schst'); var_dump($auth->isValid()); ?>
If we suppose that the user schst is blacklisted this will produce the following output in the user.log file:
2007-01-30 11:35:45|onLogin|mikey 2007-01-30 12:11:57|cancelledLogin|schst
And this would be the output of the script indicating that the first authentication was successful while the second failed:
bool(true) bool(false)
This little example shows that the Auth class doesn't know anything about the UserLoginLogging and BlackList classes, but that these classes are wired to something that happens in the Auth class.
The main classes are net::stubbles::events::stubEventDispatcher, net::stubbles::events::stubEventListener and net::stubbles::events::stubEvent. They can be loaded at once via stubClassLoader::load('net::stubbles::events::events');
The net::stubbles::events::stubEvent class is a container for events. It contains the time when the event occured, its name, the context where the event occurred (optional) and some more informations that are dependent on the type of the event. It is mostly created by the net::stubbles::events::stubEventDispatcher::trigger() method, but it is possible to create extended events outside of the event dispatcher and to use the net::stubbles::events::stubEventDispatcher::triggerEvent() method to post this event to the listeners.
The interface net::stubbles::events::stubEventListener contains two methods: handleEvent(stubEvent $event) takes the triggered event and allows the implementing class to do whatever it wants to do with the event. The second method autoremove() is used by the event dispatcher to check whether the listener should be removed after it received one event or if it should be kept and wait for other events to happen.
The net::stubbles::events::stubEventDispatcher itself takes the responsibility of registering instances of net::stubbles::events::stubEventListener for events and notifying them if the event occurred.
To register a listener an instance of net::stubbles::events::stubEventListener is required. It can be added to the dispatcher via stubEventDispatcher::register($listener, $eventName). A listener can only be registered once for the same event. A second registration of the same listener for the same event will not have any effect. To check if a listener is already registered for an event stubEventDispatcher::has($listener, $eventName) can be used. Listeners can also be removed: stubEventDispatcher::remove($listener, $eventName).
It should be noted that listeners will be notified in order of their registration. In the above example, if the listeners would have been registered in another order, namely the UserLoginLogger first and the BlackList second, the user.log file would look slightly different:
2007-01-30 11:35:45|onLogin|mikey 2007-01-30 12:11:57|onLogin|schst 2007-01-30 12:11:57|cancelledLogin|schst
The net::stubbles::events::stubEventDispatcher offers two methods to trigger an event: trigger($eventName, $context = null, array $info = array(), $queue = false, $bubble = true). Here only the event name is mandatory. This method will create an instance of net::stubbles::events::stubEvent with the event name, the context and the info. The other possibility is to create the event outside of the dispatcher, which is mainly used if an extended event should be used which may carry more data then the default implementation. For doing this the method triggerEvent(stubEvent $event, $queue = false, $bubble = true) is available. If any of both methods is called the dispatcher will notify all registered listeners via their handleEvent(stubEvent $event) method with the event instance as argument. Both methods will return the event after all listeners have been notified.
net::stubbles::events::stubEvent offers a method cancel() which enables to cancel an event. If an event is cancelled, all subsequent listeners will not be notified with the event. It is possible to check the event with stubEvent::isCancelled()in the class that triggered the event and to make any further actions dependent of whether the event was cancelled or not (see example above, where $this-userName and $this->isValid are resetted in case the event was cancelled).
Sometimes it is required that an event will not only be notified to already registered but to later registered listeners as well. To achieve this both trigger() and triggerEvent() have a parameter $queue which is optional and defaults to false. If set to true, the event dispatcher will save the event regardless of how much listeners are notified. If at a later time a new listener is registered for this event it will be instantly notified on registration. Regarding this it should be noted that if the listener returns true for its autoremove() method it will never be registered if it is notified with a queued event.
Events will be removed from the queue if they are cancelled. If the event is triggered and should be added to the queue it will not be added if one of the notified listeners cancels the event.
A list of queued events can be retrieved by a call of stubEventDispatcher::getQueuedEvents(). The amount of queued events is accessable via stubEventDispatcher::countQueuedEvents() while all queued events can be cleared via stubEventDispatcher::clearQueuedEvents().
Even if it looks like net::stubbles::events::stubEventDispatcher is a singleton this is not correct at all. It is possible to have differant instances that are distinguished by their name. This allows nesting the dispatchers:
#php <?php class SpecialListener implements stubEventListener { public function handleEvent(Event $event) { echo $event->getContext(); } } $globalDispatcher = stubEventDispatcher::getInstance(); // default call returns the global dispatcher $specialDispatcher = stubEventDispatcher::getInstance('myDispatcher'); $specialDispatcher->setParentDispatcher($globalDispatcher); $globalDispatcher->register(new SpecialListener(), 'onEvent'); $specialDispatcher->register(new SpecialListener(), 'onEvent'); $specialDispatcher->trigger('onEvent', 'hello '); $specialDispatcher->trigger('onEvent', 'world', array(), false, false); ?>
The result: hello hello world
While the first event has bubbled from the special dispatcher to the global dispatcher, the second has not. This behaviour can be controlled via the parameter $bubble of the trigger() and triggerEvent() methods which defaults to true. It may be useful to have dispatchers in components which should not influence other or be influenced by other components.
If an event gets queued and bubbled it will be queued in all event dispatcher instances where it bubbles through.
It is possible to configure the event system with an XML file. This shows an example configuration:
#xml <?xml version="1.0" encoding="iso-8859-1"?> <xj:configuration xmlns:xj="http://xjconf.net/XJConf" xmlns:cfg="http://stubbles.net/util/XJConf" xmlns="http://stubbles.net/events"> <dispatcher id="test"> <register eventName="onLogin"> <eventListener type="org::stubbles::examples::events::UserLoginLogging"/> </register> <registerLazy eventName="onLogin"> <lazyEventListener class="org::stubbles::examples::events::BlackList"/> </registerlazy> <registerCallback eventName="onLoginSubmit"> <callbackListener class="org::stubbles::examples::events::Auth" method="isValid" autoRemove="false"/> </registercallback> </dispatcher> <dispatcher /> </xj:configuration>
Here, two dispatcher instances are configured. First one that has the id test and gets three different listeners on two different events. There are three ways to register the listener:
- With <register></register>, an instance of the declared <eventlistener></eventlistener> class will be added to the dispatcher instance.
2. With <lazyeventlistener></lazyeventlistener> the declared <lazyeventlistener></lazyeventlistener> class will be used, but not instantiated until the event occurs. 3. With <registercallback></registercallback> you can register classes that do not implement the <tt>stubEventListener</tt> interface.
You need to make sure that the classes you register do not require any arguments within their constructor.
The second dispatcher will have the default id __global.
If you use this configuration and want to stack event dispatcher instances in order to make use of event bubbling, you must set the parent dispatcher instances manually. There is no possible way to support this within the configuration file.
You need the net&amp;amp&#59;amp&amp;&#35;59&#59;&amp;amp&#59;&amp;&#35;35&#59;35&amp;&#35;59&#59;58&amp;amp&#59;&amp;&#35;35&#59;59&amp;&#35;59&#59;&amp;amp&#59;amp&amp;&#35;59&#59;&amp;amp&#59;&amp;&#35;35&#59;35&amp;&#35;59&#59;58&amp;amp&#59;&amp;&#35;35&#59;59&amp;&#35;59&#59;stubbles&amp;amp&#59;amp&amp;&#35;59&#59;&amp;amp&#59;&amp;&#35;35&#59;35&amp;&#35;59&#59;58&amp;amp&#59;&amp;&#35;35&#59;59&amp;&#35;59&#59;&amp;amp&#59;amp&amp;&#35;59&#59;&amp;amp&#59;&amp;&#35;35&#59;35&amp;&#35;59&#59;58&amp;amp&#59;&amp;&#35;35&#59;59&amp;&#35;59&#59;events&amp;amp&#59;amp&amp;&#35;59&#59;&amp;amp&#59;&amp;&#35;35&#59;35&amp;&#35;59&#59;58&amp;amp&#59;&amp;&#35;35&#59;59&amp;&#35;59&#59;&amp;amp&#59;amp&amp;&#35;59&#59;&amp;amp&#59;&amp;&#35;35&#59;35&amp;&#35;59&#59;58&amp;amp&#59;&amp;&#35;35&#59;59&amp;&#35;59&#59;stubEventsXJConfInitializer to read the configuration file and create the actual dispatcher instances from them.