Browse Source

Merge branch 'ticket-4059' into 2.5

Merge the changes from pull request #1638 into 2.5.

This set of changes combines the listeners from the global and local
event managers into one ordered set of listeners. This makes the
cognitive load lower around events, as there are no longer two separate
priority queues for the global and local managers.

Closes #2105
mark_story 12 years ago
parent
commit
fc170770d5
2 changed files with 126 additions and 30 deletions
  1. 33 9
      lib/Cake/Event/CakeEventManager.php
  2. 93 21
      lib/Cake/Test/Case/Event/CakeEventManagerTest.php

+ 33 - 9
lib/Cake/Event/CakeEventManager.php

@@ -230,15 +230,12 @@ class CakeEventManager {
 			$event = new CakeEvent($event);
 			$event = new CakeEvent($event);
 		}
 		}
 
 
-		if (!$this->_isGlobal) {
-			self::instance()->dispatch($event);
-		}
-
-		if (empty($this->_listeners[$event->name()])) {
+		$listeners = $this->listeners($event->name());
+		if (empty($listeners)) {
 			return;
 			return;
 		}
 		}
 
 
-		foreach ($this->listeners($event->name()) as $listener) {
+		foreach ($listeners as $listener) {
 			if ($event->isStopped()) {
 			if ($event->isStopped()) {
 				break;
 				break;
 			}
 			}
@@ -264,15 +261,42 @@ class CakeEventManager {
  * @return array
  * @return array
  */
  */
 	public function listeners($eventKey) {
 	public function listeners($eventKey) {
-		if (empty($this->_listeners[$eventKey])) {
+		$globalListeners = array();
+		if (!$this->_isGlobal) {
+			$globalListeners = self::instance()->prioritisedListeners($eventKey);
+		}
+
+		if (empty($this->_listeners[$eventKey]) && empty($globalListeners)) {
 			return array();
 			return array();
 		}
 		}
-		ksort($this->_listeners[$eventKey]);
+
+		$listeners = $this->_listeners[$eventKey];
+		foreach ($globalListeners as $priority => $priorityQ) {
+			if (!empty($listeners[$priority])) {
+				$listeners[$priority] = array_merge($priorityQ, $listeners[$priority]);
+				unset($globalListeners[$priority]);
+			}
+		}
+		$listeners = $listeners + $globalListeners;
+
+		ksort($listeners);
 		$result = array();
 		$result = array();
-		foreach ($this->_listeners[$eventKey] as $priorityQ) {
+		foreach ($listeners as $priorityQ) {
 			$result = array_merge($result, $priorityQ);
 			$result = array_merge($result, $priorityQ);
 		}
 		}
 		return $result;
 		return $result;
 	}
 	}
 
 
+/**
+ * Returns the listeners for the specified event key indexed by priority
+ *
+ * @param string $eventKey
+ * @return array
+ */
+	public function prioritisedListeners($eventKey) {
+		if (empty($this->_listeners[$eventKey])) {
+			return array();
+		}
+		return $this->_listeners[$eventKey];
+	}
 }
 }

+ 93 - 21
lib/Cake/Test/Case/Event/CakeEventManagerTest.php

@@ -104,7 +104,7 @@ class CakeEventManagerTest extends CakeTestCase {
  * @return void
  * @return void
  */
  */
 	public function testAttachListeners() {
 	public function testAttachListeners() {
-		$manager = new CakeEventManager;
+		$manager = new CakeEventManager();
 		$manager->attach('fakeFunction', 'fake.event');
 		$manager->attach('fakeFunction', 'fake.event');
 		$expected = array(
 		$expected = array(
 			array('callable' => 'fakeFunction', 'passParams' => false)
 			array('callable' => 'fakeFunction', 'passParams' => false)
@@ -136,7 +136,7 @@ class CakeEventManagerTest extends CakeTestCase {
  * @return void
  * @return void
  */
  */
 	public function testAttachMultipleEventKeys() {
 	public function testAttachMultipleEventKeys() {
-		$manager = new CakeEventManager;
+		$manager = new CakeEventManager();
 		$manager->attach('fakeFunction', 'fake.event');
 		$manager->attach('fakeFunction', 'fake.event');
 		$manager->attach('fakeFunction2', 'another.event');
 		$manager->attach('fakeFunction2', 'another.event');
 		$manager->attach('fakeFunction3', 'another.event', array('priority' => 1, 'passParams' => true));
 		$manager->attach('fakeFunction3', 'another.event', array('priority' => 1, 'passParams' => true));
@@ -158,7 +158,7 @@ class CakeEventManagerTest extends CakeTestCase {
  * @return void
  * @return void
  */
  */
 	public function testDetach() {
 	public function testDetach() {
-		$manager = new CakeEventManager;
+		$manager = new CakeEventManager();
 		$manager->attach(array('AClass', 'aMethod'), 'fake.event');
 		$manager->attach(array('AClass', 'aMethod'), 'fake.event');
 		$manager->attach(array('AClass', 'anotherMethod'), 'another.event');
 		$manager->attach(array('AClass', 'anotherMethod'), 'another.event');
 		$manager->attach('fakeFunction', 'another.event', array('priority' => 1));
 		$manager->attach('fakeFunction', 'another.event', array('priority' => 1));
@@ -182,7 +182,7 @@ class CakeEventManagerTest extends CakeTestCase {
  * @return void
  * @return void
  */
  */
 	public function testDetachFromAll() {
 	public function testDetachFromAll() {
-		$manager = new CakeEventManager;
+		$manager = new CakeEventManager();
 		$manager->attach(array('AClass', 'aMethod'), 'fake.event');
 		$manager->attach(array('AClass', 'aMethod'), 'fake.event');
 		$manager->attach(array('AClass', 'aMethod'), 'another.event');
 		$manager->attach(array('AClass', 'aMethod'), 'another.event');
 		$manager->attach('fakeFunction', 'another.event', array('priority' => 1));
 		$manager->attach('fakeFunction', 'another.event', array('priority' => 1));
@@ -201,7 +201,7 @@ class CakeEventManagerTest extends CakeTestCase {
  * @return void
  * @return void
  */
  */
 	public function testDispatch() {
 	public function testDispatch() {
-		$manager = new CakeEventManager;
+		$manager = new CakeEventManager();
 		$listener = $this->getMock('CakeEventTestListener');
 		$listener = $this->getMock('CakeEventTestListener');
 		$anotherListener = $this->getMock('CakeEventTestListener');
 		$anotherListener = $this->getMock('CakeEventTestListener');
 		$manager->attach(array($listener, 'listenerFunction'), 'fake.event');
 		$manager->attach(array($listener, 'listenerFunction'), 'fake.event');
@@ -219,8 +219,8 @@ class CakeEventManagerTest extends CakeTestCase {
  * @return void
  * @return void
  */
  */
 	public function testDispatchWithKeyName() {
 	public function testDispatchWithKeyName() {
-		$manager = new CakeEventManager;
-		$listener = new CakeEventTestListener;
+		$manager = new CakeEventManager();
+		$listener = new CakeEventTestListener();
 		$manager->attach(array($listener, 'listenerFunction'), 'fake.event');
 		$manager->attach(array($listener, 'listenerFunction'), 'fake.event');
 		$event = 'fake.event';
 		$event = 'fake.event';
 		$manager->dispatch($event);
 		$manager->dispatch($event);
@@ -239,7 +239,7 @@ class CakeEventManagerTest extends CakeTestCase {
 			version_compare(PHPUnit_Runner_Version::id(), '3.7', '<'),
 			version_compare(PHPUnit_Runner_Version::id(), '3.7', '<'),
 			'These tests fail in PHPUnit 3.6'
 			'These tests fail in PHPUnit 3.6'
 		);
 		);
-		$manager = new CakeEventManager;
+		$manager = new CakeEventManager();
 		$listener = $this->getMock('CakeEventTestListener');
 		$listener = $this->getMock('CakeEventTestListener');
 		$anotherListener = $this->getMock('CakeEventTestListener');
 		$anotherListener = $this->getMock('CakeEventTestListener');
 		$manager->attach(array($listener, 'listenerFunction'), 'fake.event');
 		$manager->attach(array($listener, 'listenerFunction'), 'fake.event');
@@ -267,7 +267,7 @@ class CakeEventManagerTest extends CakeTestCase {
 			'These tests fail in PHPUnit 3.6'
 			'These tests fail in PHPUnit 3.6'
 		);
 		);
 
 
-		$manager = new CakeEventManager;
+		$manager = new CakeEventManager();
 		$listener = $this->getMock('CakeEventTestListener');
 		$listener = $this->getMock('CakeEventTestListener');
 		$anotherListener = $this->getMock('CakeEventTestListener');
 		$anotherListener = $this->getMock('CakeEventTestListener');
 		$manager->attach(array($listener, 'listenerFunction'), 'fake.event');
 		$manager->attach(array($listener, 'listenerFunction'), 'fake.event');
@@ -289,8 +289,8 @@ class CakeEventManagerTest extends CakeTestCase {
  * @return void
  * @return void
  */
  */
 	public function testDispatchPrioritized() {
 	public function testDispatchPrioritized() {
-		$manager = new CakeEventManager;
-		$listener = new CakeEventTestListener;
+		$manager = new CakeEventManager();
+		$listener = new CakeEventTestListener();
 		$manager->attach(array($listener, 'listenerFunction'), 'fake.event');
 		$manager->attach(array($listener, 'listenerFunction'), 'fake.event');
 		$manager->attach(array($listener, 'secondListenerFunction'), 'fake.event', array('priority' => 5));
 		$manager->attach(array($listener, 'secondListenerFunction'), 'fake.event', array('priority' => 5));
 		$event = new CakeEvent('fake.event');
 		$event = new CakeEvent('fake.event');
@@ -306,7 +306,7 @@ class CakeEventManagerTest extends CakeTestCase {
  * @return void
  * @return void
  */
  */
 	public function testDispatchPassingParams() {
 	public function testDispatchPassingParams() {
-		$manager = new CakeEventManager;
+		$manager = new CakeEventManager();
 		$listener = $this->getMock('CakeEventTestListener');
 		$listener = $this->getMock('CakeEventTestListener');
 		$anotherListener = $this->getMock('CakeEventTestListener');
 		$anotherListener = $this->getMock('CakeEventTestListener');
 		$manager->attach(array($listener, 'listenerFunction'), 'fake.event');
 		$manager->attach(array($listener, 'listenerFunction'), 'fake.event');
@@ -324,7 +324,7 @@ class CakeEventManagerTest extends CakeTestCase {
  * @return void
  * @return void
  */
  */
 	public function testAttachSubscriber() {
 	public function testAttachSubscriber() {
-		$manager = new CakeEventManager;
+		$manager = new CakeEventManager();
 		$listener = $this->getMock('CustomTestEventListener', array('secondListenerFunction'));
 		$listener = $this->getMock('CustomTestEventListener', array('secondListenerFunction'));
 		$manager->attach($listener);
 		$manager->attach($listener);
 		$event = new CakeEvent('fake.event');
 		$event = new CakeEvent('fake.event');
@@ -338,7 +338,7 @@ class CakeEventManagerTest extends CakeTestCase {
 		$event = new CakeEvent('another.event', $this, array('some' => 'data'));
 		$event = new CakeEvent('another.event', $this, array('some' => 'data'));
 		$manager->dispatch($event);
 		$manager->dispatch($event);
 
 
-		$manager = new CakeEventManager;
+		$manager = new CakeEventManager();
 		$listener = $this->getMock('CustomTestEventListener', array('listenerFunction', 'thirdListenerFunction'));
 		$listener = $this->getMock('CustomTestEventListener', array('listenerFunction', 'thirdListenerFunction'));
 		$manager->attach($listener);
 		$manager->attach($listener);
 		$event = new CakeEvent('multiple.handlers');
 		$event = new CakeEvent('multiple.handlers');
@@ -353,7 +353,7 @@ class CakeEventManagerTest extends CakeTestCase {
  * @return void
  * @return void
  */
  */
 	public function testDetachSubscriber() {
 	public function testDetachSubscriber() {
-		$manager = new CakeEventManager;
+		$manager = new CakeEventManager();
 		$listener = $this->getMock('CustomTestEventListener', array('secondListenerFunction'));
 		$listener = $this->getMock('CustomTestEventListener', array('secondListenerFunction'));
 		$manager->attach($listener);
 		$manager->attach($listener);
 		$expected = array(
 		$expected = array(
@@ -376,7 +376,7 @@ class CakeEventManagerTest extends CakeTestCase {
  */
  */
 	public function testGlobalDispatcherGetter() {
 	public function testGlobalDispatcherGetter() {
 		$this->assertInstanceOf('CakeEventManager', CakeEventManager::instance());
 		$this->assertInstanceOf('CakeEventManager', CakeEventManager::instance());
-		$manager = new CakeEventManager;
+		$manager = new CakeEventManager();
 
 
 		CakeEventManager::instance($manager);
 		CakeEventManager::instance($manager);
 		$this->assertSame($manager, CakeEventManager::instance());
 		$this->assertSame($manager, CakeEventManager::instance());
@@ -388,13 +388,14 @@ class CakeEventManagerTest extends CakeTestCase {
  * @return void
  * @return void
  */
  */
 	public function testDispatchWithGlobal() {
 	public function testDispatchWithGlobal() {
-		$generalManager = $this->getMock('CakeEventManager', array('dispatch'));
-		$manager = new CakeEventManager;
+		$generalManager = $this->getMock('CakeEventManager', array('prioritisedListeners'));
+		$manager = new CakeEventManager();
 		$event = new CakeEvent('fake.event');
 		$event = new CakeEvent('fake.event');
 		CakeEventManager::instance($generalManager);
 		CakeEventManager::instance($generalManager);
 
 
-		$generalManager->expects($this->once())->method('dispatch')->with($event);
+		$generalManager->expects($this->once())->method('prioritisedListeners')->with('fake.event');
 		$manager->dispatch($event);
 		$manager->dispatch($event);
+		CakeEventManager::instance(new CakeEventManager());
 	}
 	}
 
 
 /**
 /**
@@ -403,8 +404,16 @@ class CakeEventManagerTest extends CakeTestCase {
  * @return void
  * @return void
  */
  */
 	public function testStopPropagation() {
 	public function testStopPropagation() {
-		$manager = new CakeEventManager;
-		$listener = new CakeEventTestListener;
+		$generalManager = $this->getMock('CakeEventManager');
+		$manager = new CakeEventManager();
+		$listener = new CakeEventTestListener();
+
+		CakeEventManager::instance($generalManager);
+		$generalManager->expects($this->any())
+				->method('prioritisedListeners')
+				->with('fake.event')
+				->will($this->returnValue(array()));
+
 		$manager->attach(array($listener, 'listenerFunction'), 'fake.event');
 		$manager->attach(array($listener, 'listenerFunction'), 'fake.event');
 		$manager->attach(array($listener, 'stopListener'), 'fake.event', array('priority' => 8));
 		$manager->attach(array($listener, 'stopListener'), 'fake.event', array('priority' => 8));
 		$manager->attach(array($listener, 'secondListenerFunction'), 'fake.event', array('priority' => 5));
 		$manager->attach(array($listener, 'secondListenerFunction'), 'fake.event', array('priority' => 5));
@@ -413,5 +422,68 @@ class CakeEventManagerTest extends CakeTestCase {
 
 
 		$expected = array('secondListenerFunction');
 		$expected = array('secondListenerFunction');
 		$this->assertEquals($expected, $listener->callStack);
 		$this->assertEquals($expected, $listener->callStack);
+		CakeEventManager::instance(new CakeEventManager());
 	}
 	}
+
+/**
+ * Tests event dispatching using priorities
+ *
+ * @return void
+ */
+	public function testDispatchPrioritizedWithGlobal() {
+		$generalManager = $this->getMock('CakeEventManager');
+		$manager = new CakeEventManager();
+		$listener = new CustomTestEventListener();
+		$event = new CakeEvent('fake.event');
+
+		CakeEventManager::instance($generalManager);
+		$generalManager->expects($this->any())
+				->method('prioritisedListeners')
+				->with('fake.event')
+				->will($this->returnValue(
+					array(11 => array(
+						array('callable' => array($listener, 'secondListenerFunction'), 'passParams' => false)
+					))
+				));
+
+		$manager->attach(array($listener, 'listenerFunction'), 'fake.event');
+		$manager->attach(array($listener, 'thirdListenerFunction'), 'fake.event', array('priority' => 15));
+
+		$manager->dispatch($event);
+
+		$expected = array('listenerFunction', 'secondListenerFunction', 'thirdListenerFunction');
+		$this->assertEquals($expected, $listener->callStack);
+		CakeEventManager::instance(new CakeEventManager());
+	}
+
+/**
+ * Tests event dispatching using priorities
+ *
+ * @return void
+ */
+	public function testDispatchGlobalBeforeLocal() {
+		$generalManager = $this->getMock('CakeEventManager');
+		$manager = new CakeEventManager();
+		$listener = new CustomTestEventListener();
+		$event = new CakeEvent('fake.event');
+
+		CakeEventManager::instance($generalManager);
+		$generalManager->expects($this->any())
+				->method('prioritisedListeners')
+				->with('fake.event')
+				->will($this->returnValue(
+					array(10 => array(
+						array('callable' => array($listener, 'listenerFunction'), 'passParams' => false)
+					))
+				));
+
+		$manager->attach(array($listener, 'secondListenerFunction'), 'fake.event');
+
+		$manager->dispatch($event);
+
+		$expected = array('listenerFunction', 'secondListenerFunction');
+		$this->assertEquals($expected, $listener->callStack);
+		CakeEventManager::instance(new CakeEventManager());
+	}
+
 }
 }