Skip to content
This repository has been archived by the owner on Jan 29, 2020. It is now read-only.

Commit

Permalink
Merging develop to master in preparation for 3.1.0 release
Browse files Browse the repository at this point in the history
  • Loading branch information
weierophinney committed Dec 19, 2016
2 parents 70ca7e1 + 5ef4add commit 3b884eb
Show file tree
Hide file tree
Showing 6 changed files with 151 additions and 85 deletions.
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,24 @@

All notable changes to this project will be documented in this file, in reverse chronological order by release.

## 3.1.0 - TBD

### Added

- Nothing.

### Deprecated

- Nothing.

### Removed

- Nothing.

### Fixed

- Nothing.

## 3.0.2 - TBD

### Added
Expand Down
142 changes: 83 additions & 59 deletions src/EventManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,22 @@ class EventManager implements EventManagerInterface
/**
* Subscribed events and their listeners
*
* STRUCTURE:
* [
* <string name> => [
* <int priority> => [
* 0 => [<callable listener>, ...]
* ],
* ...
* ],
* ...
* ]
*
* NOTE:
* This structure helps us to reuse the list of listeners
* instead of first iterating over it and generating a new one
* -> In result it improves performance by up to 25% even if it looks a bit strange
*
* @var array[]
*/
protected $events = [];
Expand Down Expand Up @@ -116,8 +132,14 @@ public function trigger($eventName, $target = null, $argv = [])
{
$event = clone $this->eventPrototype;
$event->setName($eventName);
$event->setTarget($target);
$event->setParams($argv);

if ($target !== null) {
$event->setTarget($target);
}

if ($argv) {
$event->setParams($argv);
}

return $this->triggerListeners($event);
}
Expand All @@ -129,8 +151,14 @@ public function triggerUntil(callable $callback, $eventName, $target = null, $ar
{
$event = clone $this->eventPrototype;
$event->setName($eventName);
$event->setTarget($target);
$event->setParams($argv);

if ($target !== null) {
$event->setTarget($target);
}

if ($argv) {
$event->setParams($argv);
}

return $this->triggerListeners($event, $callback);
}
Expand Down Expand Up @@ -164,8 +192,7 @@ public function attach($eventName, callable $listener, $priority = 1)
));
}

$this->events[$eventName][((int) $priority) . '.0'][] = $listener;

$this->events[$eventName][(int) $priority][0][] = $listener;
return $listener;
}

Expand Down Expand Up @@ -197,36 +224,35 @@ public function detach(callable $listener, $eventName = null, $force = false)
}

foreach ($this->events[$eventName] as $priority => $listeners) {
foreach ($listeners as $index => $evaluatedListener) {
foreach ($listeners[0] as $index => $evaluatedListener) {
if ($evaluatedListener !== $listener) {
continue;
}

// Found the listener; remove it.
unset($this->events[$eventName][$priority][$index]);
unset($this->events[$eventName][$priority][0][$index]);

// If the queue for the given priority is empty, remove it.
if (empty($this->events[$eventName][$priority])) {
if (empty($this->events[$eventName][$priority][0])) {
unset($this->events[$eventName][$priority]);
break;
}
}
}

// If the queue for the given event is empty, remove it.
if (empty($this->events[$eventName])) {
unset($this->events[$eventName]);
break;
}
// If the queue for the given event is empty, remove it.
if (empty($this->events[$eventName])) {
unset($this->events[$eventName]);
}
}

/**
* @inheritDoc
*/
public function clearListeners($event)
public function clearListeners($eventName)
{
if (isset($this->events[$event])) {
unset($this->events[$event]);
if (isset($this->events[$eventName])) {
unset($this->events[$eventName]);
}
}

Expand Down Expand Up @@ -262,58 +288,56 @@ protected function triggerListeners(EventInterface $event, callable $callback =
throw new Exception\RuntimeException('Event is missing a name; cannot trigger!');
}

// Initial value of stop propagation flag should be false
$event->stopPropagation(false);

$responses = new ResponseCollection();
if (isset($this->events[$name])) {
$listOfListenersByPriority = $this->events[$name];

foreach ($this->getListenersByEventName($name) as $listener) {
$response = $listener($event);
$responses->push($response);

// If the event was asked to stop propagating, do so
if ($event->propagationIsStopped()) {
$responses->setStopped(true);
break;
if (isset($this->events['*'])) {
foreach ($this->events['*'] as $priority => $listOfListeners) {
$listOfListenersByPriority[$priority][] = $listOfListeners[0];
}
}
} elseif (isset($this->events['*'])) {
$listOfListenersByPriority = $this->events['*'];
} else {
$listOfListenersByPriority = [];
}

// If the result causes our validation callback to return true,
// stop propagation
if ($callback && $callback($response)) {
$responses->setStopped(true);
break;
if ($this->sharedManager) {
foreach ($this->sharedManager->getListeners($this->identifiers, $name) as $priority => $listeners) {
$listOfListenersByPriority[$priority][] = $listeners;
}
}

return $responses;
}

/**
* Get listeners for the currently triggered event.
*
* @param string $eventName
* @return callable[]
*/
private function getListenersByEventName($eventName)
{
$listeners = array_merge_recursive(
isset($this->events[$eventName]) ? $this->events[$eventName] : [],
isset($this->events['*']) ? $this->events['*'] : [],
$this->sharedManager ? $this->sharedManager->getListeners($this->identifiers, $eventName) : []
);

krsort($listeners, SORT_NUMERIC);
// Sort by priority in reverse order
krsort($listOfListenersByPriority);

$listenersForEvent = [];
// Initial value of stop propagation flag should be false
$event->stopPropagation(false);

foreach ($listeners as $priority => $listenersByPriority) {
foreach ($listenersByPriority as $listener) {
// Performance note: after some testing, it appears that accumulating listeners and sending
// them at the end of the method is FASTER than using generators (ie. yielding)
$listenersForEvent[] = $listener;
// Execute listeners
$responses = new ResponseCollection();
foreach ($listOfListenersByPriority as $listOfListeners) {
foreach ($listOfListeners as $listeners) {
foreach ($listeners as $listener) {
$response = $listener($event);
$responses->push($response);

// If the event was asked to stop propagating, do so
if ($event->propagationIsStopped()) {
$responses->setStopped(true);
return $responses;
}

// If the result causes our validation callback to return true,
// stop propagation
if ($callback && $callback($response)) {
$responses->setStopped(true);
return $responses;
}
}
}
}

return $listenersForEvent;
return $responses;
}
}
54 changes: 35 additions & 19 deletions src/SharedEventManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public function attach($identifier, $event, callable $listener, $priority = 1)
));
}

$this->identifiers[$identifier][$event][((int) $priority) . '.0'][] = $listener;
$this->identifiers[$identifier][$event][(int) $priority][] = $listener;
}

/**
Expand Down Expand Up @@ -152,8 +152,8 @@ public function detach(callable $listener, $identifier = null, $eventName = null
/**
* Retrieve all listeners for a given identifier and event
*
* @param array $identifiers
* @param string $eventName
* @param string[] $identifiers
* @param string $eventName
* @return array[]
* @throws Exception\InvalidArgumentException
*/
Expand All @@ -166,7 +166,7 @@ public function getListeners(array $identifiers, $eventName)
));
}

$listeners = [];
$returnListeners = [];

foreach ($identifiers as $identifier) {
if ('*' === $identifier || ! is_string($identifier) || empty($identifier)) {
Expand All @@ -176,34 +176,50 @@ public function getListeners(array $identifiers, $eventName)
));
}

$listenersByIdentifier = isset($this->identifiers[$identifier]) ? $this->identifiers[$identifier] : [];

$listeners = array_merge_recursive(
$listeners,
isset($listenersByIdentifier[$eventName]) ? $listenersByIdentifier[$eventName] : [],
isset($listenersByIdentifier['*']) ? $listenersByIdentifier['*'] : []
);
if (isset($this->identifiers[$identifier])) {
$listenersByIdentifier = $this->identifiers[$identifier];
if (isset($listenersByIdentifier[$eventName])) {
foreach ($listenersByIdentifier[$eventName] as $priority => $listeners) {
$returnListeners[$priority][] = $listeners;
}
}
if (isset($listenersByIdentifier['*'])) {
foreach ($listenersByIdentifier['*'] as $priority => $listeners) {
$returnListeners[$priority][] = $listeners;
}
}
}
}

if (isset($this->identifiers['*']) && ! in_array('*', $identifiers)) {
if (isset($this->identifiers['*'])) {
$wildcardIdentifier = $this->identifiers['*'];
if (isset($wildcardIdentifier[$eventName])) {
foreach ($wildcardIdentifier[$eventName] as $priority => $listeners) {
$returnListeners[$priority][] = $listeners;
}
}
if (isset($wildcardIdentifier['*'])) {
foreach ($wildcardIdentifier['*'] as $priority => $listeners) {
$returnListeners[$priority][] = $listeners;
}
}
}

$listeners = array_merge_recursive(
$listeners,
isset($wildcardIdentifier[$eventName]) ? $wildcardIdentifier[$eventName] : [],
isset($wildcardIdentifier['*']) ? $wildcardIdentifier['*'] : []
);
foreach ($returnListeners as $priority => $listOfListeners) {
// Argument unpacking requires PHP-5.6
// $listeners[$priority] = array_merge(...$listOfListeners);
$returnListeners[$priority] = call_user_func_array('array_merge', $listOfListeners);
}

return $listeners;
return $returnListeners;
}

/**
* @inheritDoc
*/
public function clearListeners($identifier, $eventName = null)
{
if (! array_key_exists($identifier, $this->identifiers)) {
if (! isset($this->identifiers[$identifier])) {
return false;
}

Expand Down
11 changes: 7 additions & 4 deletions src/Test/EventListenerIntrospectionTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,16 @@ private function getListenersForEvent($event, EventManager $events, $withPriorit
{
$r = new ReflectionProperty($events, 'events');
$r->setAccessible(true);
$listeners = $r->getValue($events);
$internal = $r->getValue($events);

if (! isset($listeners[$event])) {
return $this->traverseListeners([]);
$listeners = [];
foreach (isset($internal[$event]) ? $internal[$event] : [] as $p => $listOfListeners) {
foreach ($listOfListeners as $l) {
$listeners[$p] = isset($listeners[$p]) ? array_merge($listeners[$p], $l) : $l;
}
}

return $this->traverseListeners($listeners[$event], $withPriority);
return $this->traverseListeners($listeners, $withPriority);
}

/**
Expand Down
7 changes: 6 additions & 1 deletion test/EventManagerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,12 @@ public function getListenersForEvent($event, EventManager $manager)
$r->setAccessible(true);
$events = $r->getValue($manager);

return isset($events[$event]) ? $events[$event] : [];
$listenersByPriority = isset($events[$event]) ? $events[$event] : [];
foreach ($listenersByPriority as $priority => & $listeners) {
$listeners = $listeners[0];
}

return $listenersByPriority;
}

public function testAttachShouldAddListenerToEvent()
Expand Down
4 changes: 2 additions & 2 deletions test/SharedEventManagerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public function setUp()

public function getListeners(SharedEventManager $manager, array $identifiers, $event, $priority = 1)
{
$priority = (int) $priority . '.0';
$priority = (int) $priority;
$listeners = $manager->getListeners($identifiers, $event);
if (! isset($listeners[$priority])) {
return [];
Expand Down Expand Up @@ -247,7 +247,7 @@ public function testClearListenersDoesNothingIfNoEventsRegisteredForIdentifier()
$this->manager->clearListeners('IDENTIFIER', 'EVENT');

// getListeners() always pulls in wildcard listeners
$this->assertEquals(['1.0' => [
$this->assertEquals([1 => [
$this->callback,
]], $this->manager->getListeners([ 'IDENTIFIER' ], 'EVENT'));
}
Expand Down

0 comments on commit 3b884eb

Please sign in to comment.