diff --git a/src/EventManager.php b/src/EventManager.php index cfe1db0..e3cf290 100644 --- a/src/EventManager.php +++ b/src/EventManager.php @@ -254,17 +254,22 @@ public function triggerUntil($event, $target, $argv = null, $callback = null) * executed. By default, this value is 1; however, you may set it for any * integer value. Higher values have higher priority (i.e., execute first). * - * @param string|ListenerAggregate $event + * You can specify "*" for the event name. In such cases, the listener will + * be triggered for every event. + * + * @param string|array|ListenerAggregate $event An event or array of event names. If a ListenerAggregate, proxies to {@link attachAggregate()}. * @param callback|int $callback If string $event provided, expects PHP callback; for a ListenerAggregate $event, this will be the priority * @param int $priority If provided, the priority at which to register the callback * @return CallbackHandler|mixed CallbackHandler if attaching callback (to allow later unsubscribe); mixed if attaching aggregate */ public function attach($event, $callback = null, $priority = 1) { + // Proxy ListenerAggregate arguments to attachAggregate() if ($event instanceof ListenerAggregate) { return $this->attachAggregate($event, $callback); } + // Null callback is invalid if (null === $callback) { throw new Exception\InvalidArgumentException(sprintf( '%s: expects a callback; none provided', @@ -272,10 +277,24 @@ public function attach($event, $callback = null, $priority = 1) )); } + // Array of events should be registered individually, and return an array of all listeners + if (is_array($event)) { + $listeners = array(); + foreach ($event as $name) { + $listeners[] = $this->attach($name, $callback, $priority); + } + return $listeners; + } + + // If we don't have a priority queue for the event yet, create one if (empty($this->events[$event])) { $this->events[$event] = new PriorityQueue(); } + + // Create a callback handler, setting the event and priority in its metadata $listener = new CallbackHandler($callback, array('event' => $event, 'priority' => $priority)); + + // Inject the callback handler into the queue $this->events[$event]->insert($listener, $priority); return $listener; } @@ -413,24 +432,24 @@ protected function triggerListeners($event, EventDescription $e, $callback = nul $responses = new ResponseCollection; $listeners = $this->getListeners($event); - // add static listeners to the list of listeners + // Add static/wildcard listeners to the list of listeners, // but don't modify the listeners object - $staticListeners = $this->getStaticListeners($event); - if (count($staticListeners)) { + $staticListeners = $this->getStaticListeners($event); + $staticWildcardListeners = $this->getStaticListeners('*'); + $wildcardListeners = $this->getListeners('*'); + if (count($staticListeners) || count($staticWildcardListeners) || count($wildcardListeners)) { $listeners = clone $listeners; - foreach ($staticListeners as $listener) { - $priority = $listener->getMetadatum('priority'); - if (null === $priority) { - $priority = 1; - } elseif (is_array($priority)) { - // If we have an array, likely using PriorityQueue. Grab first - // element of the array, as that's the actual priority. - $priority = array_shift($priority); - } - $listeners->insert($listener, $priority); - } } + // Static listeners on this specific event + $this->insertListeners($listeners, $staticListeners); + + // Static wildcard listeners + $this->insertListeners($listeners, $staticWildcardListeners); + + // Add wildcard listeners + $this->insertListeners($listeners, $wildcardListeners); + if ($listeners->isEmpty()) { return $responses; } @@ -492,4 +511,32 @@ protected function getStaticListeners($event) return $staticListeners; } + + /** + * Add listeners to the master queue of listeners + * + * Used to inject static listeners and wildcard listeners. + * + * @param PriorityQueue $masterListeners + * @param PriorityQueue $listeners + * @return void + */ + protected function insertListeners($masterListeners, $listeners) + { + if (!count($listeners)) { + return; + } + + foreach ($listeners as $listener) { + $priority = $listener->getMetadatum('priority'); + if (null === $priority) { + $priority = 1; + } elseif (is_array($priority)) { + // If we have an array, likely using PriorityQueue. Grab first + // element of the array, as that's the actual priority. + $priority = array_shift($priority); + } + $masterListeners->insert($listener, $priority); + } + } } diff --git a/test/EventManagerTest.php b/test/EventManagerTest.php index 0d6cdf5..3200603 100644 --- a/test/EventManagerTest.php +++ b/test/EventManagerTest.php @@ -21,7 +21,9 @@ namespace ZendTest\EventManager; -use Zend\EventManager\Event, +use ArrayIterator, + stdClass, + Zend\EventManager\Event, Zend\EventManager\EventDescription, Zend\EventManager\EventManager, Zend\EventManager\ResponseCollection, @@ -69,6 +71,37 @@ public function testAttachShouldAddEventIfItDoesNotExist() $this->assertContains('test', $events); } + public function testAllowsPassingArrayOfEventNamesWhenAttaching() + { + $callback = function ($e) { + return $e->getName(); + }; + $this->events->attach(array('foo', 'bar'), $callback); + + foreach (array('foo', 'bar') as $event) { + $listeners = $this->events->getListeners($event); + $this->assertTrue(count($listeners) > 0); + foreach ($listeners as $listener) { + $this->assertSame($callback, $listener->getCallback()); + } + } + } + + public function testPassingArrayOfEventNamesWhenAttachingReturnsArrayOfCallbackHandlers() + { + $callback = function ($e) { + return $e->getName(); + }; + $listeners = $this->events->attach(array('foo', 'bar'), $callback); + + $this->assertInternalType('array', $listeners); + + foreach ($listeners as $listener) { + $this->assertInstanceOf('Zend\Stdlib\CallbackHandler', $listener); + $this->assertSame($callback, $listener->getCallback()); + } + } + public function testDetachShouldRemoveListenerFromEvent() { $listener = $this->events->attach('test', array($this, __METHOD__)); @@ -539,11 +572,26 @@ public function testIdentifierGetterSettersWorkWithArrays() public function testIdentifierGetterSettersWorkWithTraversables() { - $identifiers = new \ArrayIterator(array('foo', 'bar')); + $identifiers = new ArrayIterator(array('foo', 'bar')); $this->assertInstanceOf('Zend\EventManager\EventManager', $this->events->setIdentifiers($identifiers)); $this->assertSame($this->events->getIdentifiers(), (array) $identifiers); - $identifiers = new \ArrayIterator(array('foo', 'bar', 'baz')); + $identifiers = new ArrayIterator(array('foo', 'bar', 'baz')); $this->assertInstanceOf('Zend\EventManager\EventManager', $this->events->addIdentifiers($identifiers)); $this->assertSame($this->events->getIdentifiers(), (array) $identifiers); } + + public function testListenersAttachedWithWildcardAreTriggeredForAllEvents() + { + $test = new stdClass; + $test->events = array(); + $callback = function($e) use ($test) { + $test->events[] = $e->getName(); + }; + + $this->events->attach('*', $callback); + foreach (array('foo', 'bar', 'baz') as $event) { + $this->events->trigger($event); + $this->assertContains($event, $test->events); + } + } } diff --git a/test/StaticEventManagerTest.php b/test/StaticEventManagerTest.php index d9b3b30..140f191 100644 --- a/test/StaticEventManagerTest.php +++ b/test/StaticEventManagerTest.php @@ -20,9 +20,11 @@ */ namespace ZendTest\EventManager; -use Zend\EventManager\StaticEventManager, + +use PHPUnit_Framework_TestCase as TestCase, + stdClass, Zend\EventManager\EventManager, - PHPUnit_Framework_TestCase as TestCase; + Zend\EventManager\StaticEventManager; /** * @category Zend @@ -83,6 +85,28 @@ public function testCanAttachCallbackToEvent() $this->assertTrue($found, 'Did not find listener!'); } + public function testCanAttachCallbackToMultipleEventsAtOnce() + { + $events = StaticEventManager::getInstance(); + $events->attach('bar', array('foo', 'test'), array($this, __FUNCTION__)); + $this->assertContains('foo', $events->getEvents('bar')); + $this->assertContains('test', $events->getEvents('bar')); + $expected = array($this, __FUNCTION__); + foreach (array('foo', 'test') as $event) { + $found = false; + $listeners = $events->getListeners('bar', $event); + $this->assertInstanceOf('Zend\Stdlib\PriorityQueue', $listeners); + $this->assertTrue(0 < count($listeners), 'Empty listeners!'); + foreach ($listeners as $listener) { + if ($expected === $listener->getCallback()) { + $found = true; + break; + } + } + $this->assertTrue($found, 'Did not find listener!'); + } + } + public function testCanAttachSameEventToMultipleResourcesAtOnce() { $events = StaticEventManager::getInstance(); @@ -105,6 +129,49 @@ public function testCanAttachSameEventToMultipleResourcesAtOnce() } } + public function testCanAttachCallbackToMultipleEventsOnMultipleResourcesAtOnce() + { + $events = StaticEventManager::getInstance(); + $events->attach(array('bar', 'baz'), array('foo', 'test'), array($this, __FUNCTION__)); + $this->assertContains('foo', $events->getEvents('bar')); + $this->assertContains('test', $events->getEvents('bar')); + $expected = array($this, __FUNCTION__); + foreach (array('bar', 'baz') as $resource) { + foreach (array('foo', 'test') as $event) { + $found = false; + $listeners = $events->getListeners($resource, $event); + $this->assertInstanceOf('Zend\Stdlib\PriorityQueue', $listeners); + $this->assertTrue(0 < count($listeners), 'Empty listeners!'); + foreach ($listeners as $listener) { + if ($expected === $listener->getCallback()) { + $found = true; + break; + } + } + $this->assertTrue($found, 'Did not find listener!'); + } + } + } + + public function testListenersAttachedUsingWildcardEventWillBeTriggeredByResource() + { + $test = new stdClass; + $test->events = array(); + $callback = function($e) use ($test) { + $test->events[] = $e->getName(); + }; + + $staticEvents = StaticEventManager::getInstance(); + $staticEvents->attach('bar', '*', $callback); + + $events = new EventManager('bar'); + + foreach (array('foo', 'bar', 'baz') as $event) { + $events->trigger($event); + $this->assertContains($event, $test->events); + } + } + public function testCanDetachListenerFromResource() { $events = StaticEventManager::getInstance();