Skip to content

Commit

Permalink
[EventDispatcher] refactored the code
Browse files Browse the repository at this point in the history
 * The array returned by getListeners() now removes the listener hash as the key (as this is an implementation detail)
 * The sort method now guarantees that a listener registered before another will stay in the same order even for the same priority (for BC)
 * Made various optimizations
  • Loading branch information
fabpot committed Mar 18, 2011
1 parent 69d324e commit 136b23e
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 88 deletions.
120 changes: 48 additions & 72 deletions src/Symfony/Component/EventDispatcher/EventDispatcher.php
Expand Up @@ -34,53 +34,31 @@
* @author Jonathan Wage <jonwage@gmail.com>
* @author Roman Borschel <roman@code-factory.org>
* @author Bernhard Schussek <bschussek@gmail.com>
* @author Fabien Potencier <fabien@symfony.com>
*/
class EventDispatcher implements EventDispatcherInterface
{
/**
* Map of registered listeners.
* <event> => (<objecthash> => <listener>)
*
* @var array
*/
private $listeners = array();

/**
* Map of priorities by the object hashes of their listeners.
* <event> => (<objecthash> => <priority>)
*
* This property is used for listener sorting.
*
* @var array
*/
private $priorities = array();

/**
* Stores which event listener lists are currently sorted.
* <event> => <sorted>
*
* @var array
*/
private $sorted = array();

/**
* @see EventDispatcherInterface::dispatch
*/
public function dispatch($eventName, Event $event = null)
{
if (isset($this->listeners[$eventName])) {
if (null === $event) {
$event = new Event();
}
if (!isset($this->listeners[$eventName])) {
return;
}

$this->sortListeners($eventName);
if (null === $event) {
$event = new Event();
}

foreach ($this->listeners[$eventName] as $listener) {
$this->triggerListener($listener, $eventName, $event);
foreach ($this->getListeners($eventName) as $listener) {
$this->triggerListener($listener, $eventName, $event);

if ($event->isPropagationStopped()) {
break;
}
if ($event->isPropagationStopped()) {
break;
}
}
}
Expand All @@ -90,45 +68,52 @@ public function dispatch($eventName, Event $event = null)
*/
public function getListeners($eventName = null)
{
if ($eventName) {
$this->sortListeners($eventName);
if (null !== $eventName) {
if (!isset($this->sorted[$eventName])) {
$this->sortListeners($eventName);
}

return $this->listeners[$eventName];
return $this->sorted[$eventName];
}

$sorted = array();
foreach ($this->listeners as $eventName => $listeners) {
$this->sortListeners($eventName);
if (!isset($this->sorted[$eventName])) {
$this->sortListeners($eventName);
}

if ($this->sorted[$eventName]) {
$sorted[$eventName] = $this->sorted[$eventName];
}
}

return $this->listeners;
return $sorted;
}

/**
* @see EventDispatcherInterface::hasListeners
*/
public function hasListeners($eventName)
public function hasListeners($eventName = null)
{
return isset($this->listeners[$eventName]) && $this->listeners[$eventName];
return (Boolean) count($this->getListeners($eventName));
}

/**
* @see EventDispatcherInterface::addListener
*/
public function addListener($eventNames, $listener, $priority = 0)
{
// Picks the hash code related to that listener
$hash = spl_object_hash($listener);

foreach ((array) $eventNames as $eventName) {
if (!isset($this->listeners[$eventName])) {
$this->listeners[$eventName] = array();
$this->priorities[$eventName] = array();
if (!isset($this->listeners[$eventName][$priority])) {
if (!isset($this->listeners[$eventName])) {
$this->listeners[$eventName] = array();
}
$this->listeners[$eventName][$priority] = array();
}

// Prevents duplicate listeners on same event (same instance only)
$this->listeners[$eventName][$hash] = $listener;
$this->priorities[$eventName][$hash] = $priority;
$this->sorted[$eventName] = false;
$this->listeners[$eventName][$priority][$hash] = $listener;
unset($this->sorted[$eventName]);
}
}

Expand All @@ -137,14 +122,16 @@ public function addListener($eventNames, $listener, $priority = 0)
*/
public function removeListener($eventNames, $listener)
{
// Picks the hash code related to that listener
$hash = spl_object_hash($listener);

foreach ((array) $eventNames as $eventName) {
// Check if actually have this listener associated
if (isset($this->listeners[$eventName][$hash])) {
unset($this->listeners[$eventName][$hash]);
unset($this->priorities[$eventName][$hash]);
if (!isset($this->listeners[$eventName])) {
continue;
}

$hash = spl_object_hash($listener);
foreach (array_keys($this->listeners[$eventName]) as $priority) {
if (isset($this->listeners[$eventName][$priority][$hash])) {
unset($this->listeners[$eventName][$priority][$hash], $this->sorted[$eventName]);
}
}
}
}
Expand Down Expand Up @@ -188,26 +175,15 @@ protected function triggerListener($listener, $eventName, Event $event)
/**
* Sorts the internal list of listeners for the given event by priority.
*
* Calling this method multiple times will not cause overhead unless you
* add new listeners. As long as no listener is added, the list for an
* event name won't be sorted twice.
*
* @param string $event The name of the event.
* @param string $eventName The name of the event.
*/
private function sortListeners($eventName)
{
if (!$this->sorted[$eventName]) {
$p = $this->priorities[$eventName];

uasort($this->listeners[$eventName], function ($a, $b) use ($p) {
$order = $p[spl_object_hash($b)] - $p[spl_object_hash($a)];
$this->sorted[$eventName] = array();

// for the same priority, force the first registered one to stay first
return 0 === $order ? 1 : $order;
});

$this->sorted[$eventName] = true;
if (isset($this->listeners[$eventName])) {
krsort($this->listeners[$eventName]);
$this->sorted[$eventName] = array_values(call_user_func_array('array_merge', $this->listeners[$eventName]));
}
}
}

Expand Up @@ -86,5 +86,5 @@ function getListeners($eventName = null);
* @return Boolean TRUE if the specified event has any listeners, FALSE
* otherwise.
*/
function hasListeners($eventName);
function hasListeners($eventName = null);
}
Expand Up @@ -60,11 +60,7 @@ public function testGetListenersSortsByPriority()
$this->dispatcher->addListener('preFoo', $listener2);
$this->dispatcher->addListener('preFoo', $listener3, 10);

$expected = array(
spl_object_hash($listener3) => $listener3,
spl_object_hash($listener2) => $listener2,
spl_object_hash($listener1) => $listener1,
);
$expected = array($listener3, $listener2, $listener1);

$this->assertSame($expected, $this->dispatcher->getListeners('preFoo'));
}
Expand All @@ -86,16 +82,8 @@ public function testGetAllListenersSortsByPriority()
$this->dispatcher->addListener('postFoo', $listener6, 10);

$expected = array(
'preFoo' => array(
spl_object_hash($listener3) => $listener3,
spl_object_hash($listener2) => $listener2,
spl_object_hash($listener1) => $listener1,
),
'postFoo' => array(
spl_object_hash($listener6) => $listener6,
spl_object_hash($listener5) => $listener5,
spl_object_hash($listener4) => $listener4,
),
'preFoo' => array($listener3, $listener2, $listener1),
'postFoo' => array($listener6, $listener5, $listener4),
);

$this->assertSame($expected, $this->dispatcher->getListeners());
Expand Down

0 comments on commit 136b23e

Please sign in to comment.