Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Adding new general purpose event system, thanks to Florian Krämer for…

… the original implementation
  • Loading branch information...
commit e8044cd5285e4278fdd5180e87dee97e66268464 1 parent e4cc18c
José Lorenzo Rodríguez lorenzo authored
115 lib/Cake/Event/CakeEvent.php
View
@@ -0,0 +1,115 @@
+<?php
+/**
+ *
+ * PHP 5
+ *
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright 2005-2011, Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright Copyright 2005-2011, Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link http://cakephp.org CakePHP(tm) Project
+ * @package Cake.Observer
+ * @since CakePHP(tm) v 2.1
+ * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
+ */
+
+/**
+ * Represent the transport class of events across the system, it receives a name, and subject and an optional
+ * payload. The name can be any string that uniquely identifies the event across the application, while the subject
+ * represents the object that the event is applying to.
+ *
+ * @package Cake.Event
+ */
+class CakeEvent {
+
+/**
+ * Name of the event
+ *
+ * @var string $name
+ */
+ protected $_name = null;
+
+/**
+ * The object this event applies to (usually the same object that generates the event)
+ *
+ * @var object
+ */
+ protected $_subject;
+
+/**
+ * Custom data for the method that receives the event
+ *
+ * @var mixed $data
+ */
+ public $data = null;
+
+
+/**
+ * Flags an event as stopped or not, default is false
+ *
+ * @var boolean
+ */
+ protected $_stopped = false;
+
+/**
+ * Constructor
+ *
+ * @param string $name Name of the event
+ * @param object $subject the object that this event applies to (usually the object that is generating the event)
+ * @param mixed $data any value you wish to be transported with this event to it can be read by listeners
+ *
+ * ## Examples of usage:
+ *
+ * {{{
+ * $event = new CakeEvent('Order.afterBuy', $this, array('buyer' => $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;
+ }
+
+}
127 lib/Cake/Event/CakeEventManager.php
View
@@ -0,0 +1,127 @@
+<?php
+/**
+ *
+ * PHP 5
+ *
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright 2005-2011, Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright Copyright 2005-2011, Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link http://cakephp.org CakePHP(tm) Project
+ * @package Cake.Event
+ * @since CakePHP(tm) v 2.1
+ * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
+ */
+
+
+/**
+ * The event manager is responsible for keeping track of event listeners and pass the correct
+ * data to them, and fire them in the correct order, when associated events are triggered. You
+ * can create multiple instances of this objects to manage local events or keep a single instance
+ * and pass it around to manage all events in your app.
+ *
+ * @package Cake.Event
+ */
+class CakeEventManager {
+
+/**
+ * The default priority queue value for new attached listeners
+ *
+ * @var int
+ */
+ public static $defaultPriority = 10;
+
+/**
+ * List of listener callbacks associated to
+ *
+ * @var object $Listeners
+ */
+ protected $_listeners = array();
+
+/**
+ * Adds a new listener to an event. Listeners
+ *
+ * @param mixed $eventKey The event unique identifier name to with the callback will be associated
+ * @param callback|CakeListener PHP valid callback type or instance of CakeListener to be called
+ * when the event named with $eventKey is triggered.
+ * @return void
+ */
+ public function attach($eventKey, $callable, $options = array()) {
+ $options = $options + array('priority' => 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());
+ }
+
+}
0  lib/Cake/Event/CakeListener.php
View
No changes.
208 lib/Cake/Test/Case/Event/CakeEventManagerTest.php
View
@@ -0,0 +1,208 @@
+<?php
+/**
+ * ControllerTestCaseTest file
+ *
+ * Test Case for ControllerTestCase class
+ *
+ * PHP version 5
+ *
+ * CakePHP : Rapid Development Framework (http://cakephp.org)
+ * Copyright 2005-2011, Cake Software Foundation, Inc.
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright Copyright 2005-2011, Cake Software Foundation, Inc.
+ * @link http://cakephp.org CakePHP Project
+ * @package Cake.Test.Case.Event
+ * @since CakePHP v 2.1
+ * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
+ */
+
+App::uses('CakeEvent', 'Event');
+App::uses('CakeEventManager', 'Event');
+
+/**
+ * Mock class used to test event dispatching
+ *
+ * @package Cake.Test.Case.Event
+ */
+class CakeEventTestListener {
+
+ public $callStack = array();
+
+/**
+ * Test function to be used in event dispatching
+ *
+ * @return void
+ */
+ public function listenerFunction() {
+ $this->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);
+ }
+}
74 lib/Cake/Test/Case/Event/CakeEventTest.php
View
@@ -0,0 +1,74 @@
+<?php
+/**
+ * ControllerTestCaseTest file
+ *
+ * Test Case for ControllerTestCase class
+ *
+ * PHP version 5
+ *
+ * CakePHP : Rapid Development Framework (http://cakephp.org)
+ * Copyright 2005-2011, Cake Software Foundation, Inc.
+ *
+ * Licensed under The MIT License
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright Copyright 2005-2011, Cake Software Foundation, Inc.
+ * @link http://cakephp.org CakePHP Project
+ * @package Cake.Test.Case.Event
+ * @since CakePHP v 2.1
+ * @license MIT License (http://www.opensource.org/licenses/mit-license.php)
+ */
+
+App::uses('CakeEvent', 'Event');
+
+/**
+ * Tests the CakeEvent class functionality
+ *
+ */
+class CakeEventTest extends CakeTestCase {
+
+/**
+ * Tests the name() method
+ *
+ * @return void
+ */
+ public function testName() {
+ $event = new CakeEvent('fake.event');
+ $this->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);
+ }
+}
Please sign in to comment.
Something went wrong with that request. Please try again.