Event Listening plugin design, round four!
Posted: Sun Feb 24, 2008 5:33 am
I do this differently everytime I write this damn thing! 
For those who don't know, I write a library who's plugin system allows modification of "events" prior to those events occuring (e.g. a message being sent, a response being received from a remote server etc). The last version of this created new objects for every single event, even if that was just to send a short command (4 characters) to a remote server a completely new CommandEvent would be instantiated and relayed around a list of observers (EventListeners) who could listen for a CommandEvent and modify it accordingly or simple take note on it without modifying it.
This is in my eyes today, just a bit overkill and too heavy for something observer-ish.
Since I'm rewriting this (surprisingly rather trivial, drop-in near the end) little part of my library I'm trying to make it a bit lighter and a bit more flexible.
Things which were not so good in the old design:
* Too heavy
* There were desires for their to be the ability to "cancel" and event, but this was not implemented
So I'm trying a new approach this time.... more of a filter chain. The Event objects are not needed since they were merely wrappers, and the ability to cancel an event is trivial (break the chain).
I'm looking for critique from somebody with brains really
So here's my mock-up of how it would work. Any thoughts?
EDIT | Hmm... it seems that rather have the EventDispatcher "return" the filtered value, it should probably invoke some method on it's caller which puts it at the end of the chain.
My original code had the intention of checking for empty return values if an event was cancelled but that logic is flawed since there may sometimes be valid reasons for empty values.
For those who don't know, I write a library who's plugin system allows modification of "events" prior to those events occuring (e.g. a message being sent, a response being received from a remote server etc). The last version of this created new objects for every single event, even if that was just to send a short command (4 characters) to a remote server a completely new CommandEvent would be instantiated and relayed around a list of observers (EventListeners) who could listen for a CommandEvent and modify it accordingly or simple take note on it without modifying it.
This is in my eyes today, just a bit overkill and too heavy for something observer-ish.
Since I'm rewriting this (surprisingly rather trivial, drop-in near the end) little part of my library I'm trying to make it a bit lighter and a bit more flexible.
Things which were not so good in the old design:
* Too heavy
* There were desires for their to be the ability to "cancel" and event, but this was not implemented
So I'm trying a new approach this time.... more of a filter chain. The Event objects are not needed since they were merely wrappers, and the ability to cancel an event is trivial (break the chain).
I'm looking for critique from somebody with brains really
Code: Select all
<?php
error_reporting(E_ALL | E_STRICT);
/**
* A pretty standard FilterChain interface.
*/
interface EventListenerChain {
/**
* Filter $object through a list of Filters.
* @param string $eventType
* @param object $object
* @return object
*/
public function handleEvent($eventType, $object);
}
/**
* A container for storing and running EventListeners.
*/
interface EventDispatcher extends EventListenerChain {
/**
* Add a new EventListener.
* @param EventListener $listener
*/
public function addEventListener(EventListener $listener);
/**
* Dispatch an event of $eventType to relevant EventListeners.
* The original object, or a (compatible) modification of it should be returned.
* @param string $eventType
* @param object $object
* @return object
*/
public function dispatchEvent($eventType, $object);
}
/**
* A pretty standard Filter interface.
*/
interface EventListener {
/**
* Filter $object and run the next filter in the FilterChain.
* @param string $eventType
* @param object $object
* @param FilterChain $chain
*/
public function handleEvent($eventType, $object, EventListenerChain $chain);
}
/**
* A simple EventListener demonstration.
*/
class FooListener implements EventListener {
public function handleEvent($eventType, $object, EventListenerChain $chain) {
$chain->handleEvent($eventType, $object);
}
}
/**
* A simple EventListener demonstration.
*/
class BarListener implements EventListener {
public function handleEvent($eventType, $object, EventListenerChain $chain) {
if ('command' == $eventType) {
$object .= 'blah';
}
$chain->handleEvent($eventType, $object);
}
}
/**
* A simple event dispatcher demonstration.
*/
class SimpleDispatcher implements EventDispatcher {
private $_queue = array();
private $_used = array();
private $_result;
public function handleEvent($eventType, $object) {
if ($listener = array_shift($this->_queue)) {
$this->_used[] = $listener;
$listener->handleEvent($eventType, $object, $this);
} else {
$this->_result = $object;
}
}
public function addEventListener(EventListener $listener) {
$this->_queue[] = $listener;
}
public function dispatchEvent($eventType, $object) {
$this->_result = null;
while ($listener = array_pop($this->_used)) {
array_unshift($this->_queue, $listener);
}
$this->handleEvent($eventType, $object);
return $this->_result;
}
}
$dispatcher = new SimpleDispatcher();
$dispatcher->addEventListener(new FooListener());
$dispatcher->addEventListener(new BarListener());
echo $dispatcher->dispatchEvent('command', 'abc');
//abcblahCode: Select all
$dispatcher->dispatchEvent('command', 'abc', $this);
//whichever object calls it passes itself ready for the event result to be received