From e8044cd5285e4278fdd5180e87dee97e66268464 Mon Sep 17 00:00:00 2001 From: Jose Lorenzo Rodriguez Date: Tue, 13 Dec 2011 00:22:40 -0430 Subject: [PATCH] =?UTF-8?q?Adding=20new=20general=20purpose=20event=20syst?= =?UTF-8?q?em,=20thanks=20to=20Florian=20Kr=C3=A4mer=20for=20the=20origina?= =?UTF-8?q?l=20implementation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/Cake/Event/CakeEvent.php | 115 ++++++++++ lib/Cake/Event/CakeEventManager.php | 127 +++++++++++ lib/Cake/Event/CakeListener.php | 0 .../Test/Case/Event/CakeEventManagerTest.php | 208 ++++++++++++++++++ lib/Cake/Test/Case/Event/CakeEventTest.php | 74 +++++++ 5 files changed, 524 insertions(+) create mode 100644 lib/Cake/Event/CakeEvent.php create mode 100644 lib/Cake/Event/CakeEventManager.php create mode 100644 lib/Cake/Event/CakeListener.php create mode 100644 lib/Cake/Test/Case/Event/CakeEventManagerTest.php create mode 100644 lib/Cake/Test/Case/Event/CakeEventTest.php diff --git a/lib/Cake/Event/CakeEvent.php b/lib/Cake/Event/CakeEvent.php new file mode 100644 index 000000000..9d719d8ee --- /dev/null +++ b/lib/Cake/Event/CakeEvent.php @@ -0,0 +1,115 @@ + $userData)); + * $event = new CakeEvent('User.afterRegister', $UserModel); + * }}} + * + */ + public function __construct($name, $subject = null, $data = null) { + $this->_name = $name; + $this->data = $data; + $this->_subject = $subject; + } + +/** + * Returns the name of this event. This is usually used as the event identifier + * + * @return string + */ + public function name() { + return $this->_name; + } + + +/** + * Returns the subject of this event + * + * @return string + */ + public function subject() { + return $this->_subject; + } + +/** + * Stops the event from being used anymore + * + * @return void + */ + public function stopPropagation() { + return $this->_stopped = true; + } + +/** + * Check if the event is stopped + * + * @return boolean True if the event is stopped + */ + public function isStopped() { + return $this->_stopped; + } + +} \ No newline at end of file diff --git a/lib/Cake/Event/CakeEventManager.php b/lib/Cake/Event/CakeEventManager.php new file mode 100644 index 000000000..46990ca5c --- /dev/null +++ b/lib/Cake/Event/CakeEventManager.php @@ -0,0 +1,127 @@ + self::$defaultPriority, 'passParams' => false); + $this->_listeners[$eventKey][$options['priority']][] = array( + 'callable' => $callable, + 'passParams' => $options['passParams'], + ); + } + +/** + * Removes a listener from the active listeners. + * + * @param callback|CakeListener $callable any valid PHP callback type or an instance of CakeListener + * @return void + */ + public function detach($callable, $eventKey = null) { + if (empty($eventKey)) { + foreach (array_keys($this->_listeners) as $eventKey) { + $this->detach($callable, $eventKey); + } + return; + } + if (empty($this->_listeners[$eventKey])) { + return; + } + foreach ($this->_listeners[$eventKey] as $priority => $callables) { + foreach ($callables as $k => $callback) { + if ($callback['callable'] === $callable) { + unset($this->_listeners[$eventKey][$priority][$k]); + break; + } + } + } + } + +/** + * Dispatches a new event to all configured listeners + * + * @param mixed $event the event key name or instance of CakeEvent + * @return void + */ + public function dispatch($event) { + if (is_string($event)) { + $Event = new CakeEvent($event); + } + if (empty($this->_listeners[$event->name()])) { + return; + } + + foreach ($this->listeners($event->name()) as $listener) { + if ($event->isStopped()) { + break; + } + if ($listener['passParams'] === true) { + call_user_func_array($listener['callable'], $event->data); + } else { + call_user_func($listener['callable'], $event); + } + continue; + } + } + +/** + * Returns a list of all listeners for a eventKey in the order they should be called + * + * @param string $eventKey + * @return array + */ + public function listeners($eventKey) { + if (empty($this->_listeners[$eventKey])) { + return array(); + } + ksort($this->_listeners[$eventKey]); + return array_reduce($this->_listeners[$eventKey], 'array_merge', array()); + } + +} \ No newline at end of file diff --git a/lib/Cake/Event/CakeListener.php b/lib/Cake/Event/CakeListener.php new file mode 100644 index 000000000..e69de29bb diff --git a/lib/Cake/Test/Case/Event/CakeEventManagerTest.php b/lib/Cake/Test/Case/Event/CakeEventManagerTest.php new file mode 100644 index 000000000..f35b0aebe --- /dev/null +++ b/lib/Cake/Test/Case/Event/CakeEventManagerTest.php @@ -0,0 +1,208 @@ +callStack[] = __FUNCTION__; + } + +/** + * Test function to be used in event dispatching + * + * @return void + */ + public function secondListenerFunction() { + $this->callStack[] = __FUNCTION__; + } +} + +/** + * Tests the CakeEventManager class functionality + * + */ +class CakeEventManagerTest extends CakeTestCase { + +/** + * Tests the attach() method for a single event key in multiple queues + * + * @return void + */ + public function testAttachListeners() { + $manager = new CakeEventManager; + $manager->attach('fake.event', 'fakeFunction'); + $expected = array( + array('callable' => 'fakeFunction', 'passParams' => false) + ); + $this->assertEquals($expected, $manager->listeners('fake.event')); + + $manager->attach('fake.event', 'fakeFunction2'); + $expected[] = array('callable' => 'fakeFunction2', 'passParams' => false); + $this->assertEquals($expected, $manager->listeners('fake.event')); + + $manager->attach('fake.event', 'inQ5', array('priority' => 5)); + $manager->attach('fake.event', 'inQ1', array('priority' => 1)); + $manager->attach('fake.event', 'otherInQ5', array('priority' => 5)); + + $expected = array_merge( + array( + array('callable' => 'inQ1', 'passParams' => false), + array('callable' => 'inQ5', 'passParams' => false), + array('callable' => 'otherInQ5', 'passParams' => false) + ), + $expected + ); + $this->assertEquals($expected, $manager->listeners('fake.event')); + } + +/** + * Tests the attach() method for multiple event key in multiple queues + * + * @return void + */ + public function testAttachMultipleEventKeys() { + $manager = new CakeEventManager; + $manager->attach('fake.event', 'fakeFunction'); + $manager->attach('another.event', 'fakeFunction2'); + $manager->attach('another.event', 'fakeFunction3', array('priority' => 1, 'passParams' => true)); + $expected = array( + array('callable' => 'fakeFunction', 'passParams' => false) + ); + $this->assertEquals($expected, $manager->listeners('fake.event')); + + $expected = array( + array('callable' => 'fakeFunction3', 'passParams' => true), + array('callable' => 'fakeFunction2', 'passParams' => false) + ); + $this->assertEquals($expected, $manager->listeners('another.event')); + } + +/** + * Tests detaching an event from a event key queue + * + * @return void + */ + public function testDetach() { + $manager = new CakeEventManager; + $manager->attach('fake.event', array('AClass', 'aMethod')); + $manager->attach('another.event', array('AClass', 'anotherMethod')); + $manager->attach('another.event', 'fakeFunction', array('priority' => 1)); + + $manager->detach(array('AClass', 'aMethod'), 'fake.event'); + $this->assertEquals(array(), $manager->listeners('fake.event')); + + $manager->detach(array('AClass', 'anotherMethod'), 'another.event'); + $expected = array( + array('callable' => 'fakeFunction', 'passParams' => false) + ); + $this->assertEquals($expected, $manager->listeners('another.event')); + + $manager->detach('fakeFunction', 'another.event'); + $this->assertEquals(array(), $manager->listeners('another.event')); + } + +/** + * Tests detaching an event from all event queues + * + * @return void + */ + public function testDetachFromAll() { + $manager = new CakeEventManager; + $manager->attach('fake.event', array('AClass', 'aMethod')); + $manager->attach('another.event', array('AClass', 'aMethod')); + $manager->attach('another.event', 'fakeFunction', array('priority' => 1)); + + $manager->detach(array('AClass', 'aMethod')); + $expected = array( + array('callable' => 'fakeFunction', 'passParams' => false) + ); + $this->assertEquals($expected, $manager->listeners('another.event')); + $this->assertEquals(array(), $manager->listeners('fake.event')); + } + +/** + * Tests event dispatching + * + * @return void + */ + public function testDispatch() { + $manager = new CakeEventManager; + $listener = $this->getMock('CakeEventTestListener'); + $anotherListener = $this->getMock('CakeEventTestListener'); + $manager->attach('fake.event', array($listener, 'listenerFunction')); + $manager->attach('fake.event', array($anotherListener, 'listenerFunction')); + $event = new CakeEvent('fake.event'); + + $listener->expects($this->once())->method('listenerFunction')->with($event); + $anotherListener->expects($this->once())->method('listenerFunction')->with($event); + $manager->dispatch($event); + } + +/** + * Tests event dispatching using priorities + * + * @return void + */ + public function testDispatchPrioritized() { + $manager = new CakeEventManager; + $listener = new CakeEventTestListener; + $manager->attach('fake.event', array($listener, 'listenerFunction')); + $manager->attach('fake.event', array($listener, 'secondListenerFunction'), array('priority' => 5)); + $event = new CakeEvent('fake.event'); + $manager->dispatch($event); + + $expected = array('secondListenerFunction', 'listenerFunction'); + $this->assertEquals($expected, $listener->callStack); + } + +/** + * Tests event dispatching with passed params + * + * @return void + */ + public function testDispatchPassingParams() { + $manager = new CakeEventManager; + $listener = $this->getMock('CakeEventTestListener'); + $anotherListener = $this->getMock('CakeEventTestListener'); + $manager->attach('fake.event', array($listener, 'listenerFunction')); + $manager->attach('fake.event', array($anotherListener, 'secondListenerFunction'), array('passParams' => true)); + $event = new CakeEvent('fake.event', $this, array('some' => 'data')); + + $listener->expects($this->once())->method('listenerFunction')->with($event); + $anotherListener->expects($this->once())->method('secondListenerFunction')->with('data'); + $manager->dispatch($event); + } +} \ No newline at end of file diff --git a/lib/Cake/Test/Case/Event/CakeEventTest.php b/lib/Cake/Test/Case/Event/CakeEventTest.php new file mode 100644 index 000000000..2f5683843 --- /dev/null +++ b/lib/Cake/Test/Case/Event/CakeEventTest.php @@ -0,0 +1,74 @@ +assertEquals('fake.event', $event->name()); + } + +/** + * Tests the subject() method + * + * @return void + */ + public function testSubject() { + $event = new CakeEvent('fake.event', $this); + $this->assertSame($this, $event->subject()); + + $event = new CakeEvent('fake.event'); + $this->assertNull($event->subject()); + } + +/** + * Tests the event propagation stopping property + * + * @return void + */ + public function testPropagation() { + $event = new CakeEvent('fake.event'); + $this->assertFalse($event->isStopped()); + $event->stopPropagation(); + $this->assertTrue($event->isStopped()); + } + +/** + * Tests that it is possible to get/set custom data in a event + * + * @return void + */ + public function testEventData() { + $event = new CakeEvent('fake.event', $this, array('some' => 'data')); + $this->assertEquals(array('some' => 'data'), $event->data); + } +} \ No newline at end of file