Browse Source

Merge pull request #8853 from burzum/feature/event-stack

Implements a stack of dispatched events #7404
Mark Story 9 years ago
parent
commit
f48f83064d

+ 129 - 0
src/Event/EventList.php

@@ -0,0 +1,129 @@
+<?php
+/**
+ * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link          http://cakephp.org CakePHP(tm) Project
+ * @since         3.3.0
+ * @license       http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+namespace Cake\Event;
+
+/**
+ * The Event List
+ */
+class EventList implements \ArrayAccess, \Countable
+{
+
+    /**
+     * Events list
+     *
+     * @var array
+     */
+    protected $_events = [];
+
+    /**
+     * Empties the list of dispatched events.
+     *
+     * @return void
+     */
+    public function flush()
+    {
+        $this->_events = [];
+    }
+
+    /**
+     * Adds an event to the list when event listing is enabled.
+     *
+     * @param \Cake\Event\Event $event An event to the list of dispatched events.
+     * @return void
+     */
+    public function add(Event $event)
+    {
+        $this->_events[] = $event;
+    }
+
+    /**
+     * Whether a offset exists
+     *
+     * @link http://php.net/manual/en/arrayaccess.offsetexists.php
+     * @param mixed $offset An offset to check for.
+     * @return boole True on success or false on failure.
+     */
+    public function offsetExists($offset)
+    {
+        return isset($this->_events[$offset]);
+    }
+
+    /**
+     * Offset to retrieve
+     *
+     * @link http://php.net/manual/en/arrayaccess.offsetget.php
+     * @param mixed $offset The offset to retrieve.
+     * @return mixed Can return all value types.
+     */
+    public function offsetGet($offset)
+    {
+        if ($this->offsetExists($offset)) {
+            return $this->_events[$offset];
+        }
+        return null;
+    }
+
+    /**
+     * Offset to set
+     *
+     * @link http://php.net/manual/en/arrayaccess.offsetset.php
+     * @param mixed $offset The offset to assign the value to.
+     * @param mixed $value The value to set.
+     * @return void
+     */
+    public function offsetSet($offset, $value)
+    {
+        $this->_events[$offset] = $value;
+    }
+
+    /**
+     * Offset to unset
+     *
+     * @link http://php.net/manual/en/arrayaccess.offsetunset.php
+     * @param mixed $offset The offset to unset.
+     * @return void
+     */
+    public function offsetUnset($offset)
+    {
+        unset($this->_events[$offset]);
+    }
+
+    /**
+     * Count elements of an object
+     *
+     * @link http://php.net/manual/en/countable.count.php
+     * @return int The custom count as an integer.
+     */
+    public function count()
+    {
+        return count($this->_events);
+    }
+
+    /**
+     * Checks if an event is in the list.
+     *
+     * @param string $name Event name.
+     * @return bool
+     */
+    public function hasEvent($name)
+    {
+        foreach ($this->_events as $event) {
+            if ($event->name() === $name) {
+                return true;
+            }
+        }
+        return false;
+    }
+}

+ 83 - 0
src/Event/EventManager.php

@@ -54,6 +54,20 @@ class EventManager
     protected $_isGlobal = false;
 
     /**
+     * The event list object.
+     *
+     * @var \Cake\Event\EventList|null
+     */
+    protected $_eventList;
+
+    /**
+     * Enables automatic adding of events to the event list object if it is present.
+     *
+     * @param bool
+     */
+    protected $_trackEvents = false;
+
+    /**
      * Returns the globally available instance of a Cake\Event\EventManager
      * this is used for dispatching events attached from outside the scope
      * other managers were created. Usually for creating hook systems or inter-class
@@ -346,6 +360,9 @@ class EventManager
 
         $listeners = $this->listeners($event->name());
         if (empty($listeners)) {
+            if ($this->_trackEvents) {
+                $this->addEventToList($event);
+            }
             return $event;
         }
 
@@ -361,6 +378,10 @@ class EventManager
                 $event->result = $result;
             }
         }
+
+        if ($this->_trackEvents) {
+            $this->addEventToList($event);
+        }
         return $event;
     }
 
@@ -461,6 +482,63 @@ class EventManager
     }
 
     /**
+     * Returns the event list.
+     *
+     * @return \Cake\Event\EventList
+     */
+    public function getEventList()
+    {
+        return $this->_eventList;
+    }
+
+    /**
+     * Adds an event to the list if the event list object is present.
+     *
+     * @param \Cake\Event\Event $event An event to add to the list.
+     * @return void
+     */
+    public function addEventToList(Event $event)
+    {
+        if ($this->_eventList) {
+            $this->_eventList->add($event);
+        }
+    }
+
+    /**
+     * Enables / disables event tracking at runtime.
+     *
+     * @param bool $enabled True or false to enable / disable it.
+     * @return void
+     */
+    public function trackEvents($enabled)
+    {
+        $this->_trackEvents = (bool)$enabled;
+    }
+
+    /**
+     * Enables the listing of dispatched events.
+     *
+     * @param \Cake\Event\EventList $eventList The event list object to use.
+     * @return void
+     */
+    public function setEventList(EventList $eventList)
+    {
+        $this->_eventList = $eventList;
+        $this->_trackEvents = true;
+    }
+
+    /**
+     * Disables the listing of dispatched events.
+     *
+     * @return void
+     */
+    public function unsetEventList()
+    {
+        $this->_eventList = null;
+        $this->_trackEvents = false;
+    }
+
+    /**
      * Debug friendly object properties.
      *
      * @return array
@@ -473,6 +551,11 @@ class EventManager
         foreach ($this->_listeners as $key => $listeners) {
             $properties['_listeners'][$key] = count($listeners) . ' listener(s)';
         }
+        if ($this->_eventList) {
+            foreach ($this->_eventList as $event) {
+                $properties['_dispatchedEvents'][] = $event->name() . ' with subject ' . get_class($event->subject());
+            }
+        }
         return $properties;
     }
 }

+ 81 - 0
tests/TestCase/Event/EventListTest.php

@@ -0,0 +1,81 @@
+<?php
+/**
+ * CakePHP : Rapid Development Framework (http://cakephp.org)
+ * Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ *
+ * Licensed under The MIT License
+ * For full copyright and license information, please see the LICENSE.txt
+ * Redistributions of files must retain the above copyright notice.
+ *
+ * @copyright     Copyright (c) Cake Software Foundation, Inc. (http://cakefoundation.org)
+ * @link          http://cakephp.org CakePHP Project
+ * @since         3.3.0
+ * @license       http://www.opensource.org/licenses/mit-license.php MIT License
+ */
+namespace Cake\Test\TestCase\Event;
+
+use Cake\Event\Event;
+use Cake\Event\EventList;
+use Cake\TestSuite\TestCase;
+
+/**
+ * Tests the Cake\Event\EvenList class functionality
+ */
+class EvenListTest extends TestCase
+{
+
+    /**
+     * testAddEventAndFlush
+     *
+     * @return void
+     */
+    public function testAddEventAndFlush()
+    {
+        $eventList = new EventList();
+        $event = new Event('my_event', $this);
+        $event2 = new Event('my_second_event', $this);
+
+        $eventList->add($event);
+        $eventList->add($event2);
+        $this->assertCount(2, $eventList);
+
+        $this->assertEquals($eventList[0], $event);
+        $this->assertEquals($eventList[1], $event2);
+
+        $eventList->flush();
+
+        $this->assertCount(0, $eventList);
+    }
+
+    /**
+     * Testing implemented \ArrayAccess and \Count methods
+     *
+     * @return void
+     */
+    public function testArrayAccess()
+    {
+        $eventList = new EventList();
+        $event = new Event('my_event', $this);
+        $event2 = new Event('my_second_event', $this);
+
+        $eventList->add($event);
+        $eventList->add($event2);
+        $this->assertCount(2, $eventList);
+
+        $this->assertTrue($eventList->hasEvent('my_event'));
+        $this->assertFalse($eventList->hasEvent('does-not-exist'));
+
+        $this->assertEquals($eventList->offsetGet(0), $event);
+        $this->assertEquals($eventList->offsetGet(1), $event2);
+        $this->assertTrue($eventList->offsetExists(0));
+        $this->assertTrue($eventList->offsetExists(1));
+        $this->assertFalse($eventList->offsetExists(2));
+
+        $eventList->offsetUnset(1);
+        $this->assertCount(1, $eventList);
+
+        $eventList->flush();
+
+        $this->assertCount(0, $eventList);
+    }
+}

+ 50 - 13
tests/TestCase/Event/EventManagerTest.php

@@ -15,6 +15,7 @@
 namespace Cake\Test\TestCase\Event;
 
 use Cake\Event\Event;
+use Cake\Event\EventList;
 use Cake\Event\EventListenerInterface;
 use Cake\Event\EventManager;
 use Cake\TestSuite\TestCase;
@@ -25,7 +26,7 @@ use Cake\TestSuite\TestCase;
 class EventTestListener
 {
 
-    public $callStack = [];
+    public $callList = [];
 
     /**
      * Test function to be used in event dispatching
@@ -34,7 +35,7 @@ class EventTestListener
      */
     public function listenerFunction()
     {
-        $this->callStack[] = __FUNCTION__;
+        $this->callList[] = __FUNCTION__;
     }
 
     /**
@@ -44,7 +45,7 @@ class EventTestListener
      */
     public function secondListenerFunction()
     {
-        $this->callStack[] = __FUNCTION__;
+        $this->callList[] = __FUNCTION__;
     }
 
     /**
@@ -84,7 +85,7 @@ class CustomTestEventListenerInterface extends EventTestListener implements Even
      */
     public function thirdListenerFunction()
     {
-        $this->callStack[] = __FUNCTION__;
+        $this->callList[] = __FUNCTION__;
     }
 }
 
@@ -392,7 +393,7 @@ class EventManagerTest extends TestCase
         $manager->dispatch($event);
 
         $expected = ['listenerFunction'];
-        $this->assertEquals($expected, $listener->callStack);
+        $this->assertEquals($expected, $listener->callList);
     }
 
     /**
@@ -469,7 +470,7 @@ class EventManagerTest extends TestCase
         $manager->dispatch($event);
 
         $expected = ['secondListenerFunction', 'listenerFunction'];
-        $this->assertEquals($expected, $listener->callStack);
+        $this->assertEquals($expected, $listener->callList);
     }
 
     /**
@@ -489,7 +490,7 @@ class EventManagerTest extends TestCase
         $manager->dispatch($event);
 
         $expected = ['listenerFunction'];
-        $this->assertEquals($expected, $listener->callStack);
+        $this->assertEquals($expected, $listener->callList);
 
         $event = new Event('another.event', $this, ['some' => 'data']);
         $listener->expects($this->at(0))
@@ -599,7 +600,7 @@ class EventManagerTest extends TestCase
         $manager->dispatch($event);
 
         $expected = ['secondListenerFunction'];
-        $this->assertEquals($expected, $listener->callStack);
+        $this->assertEquals($expected, $listener->callList);
         EventManager::instance(new EventManager());
     }
 
@@ -632,7 +633,7 @@ class EventManagerTest extends TestCase
         $manager->dispatch($event);
 
         $expected = ['listenerFunction', 'secondListenerFunction', 'thirdListenerFunction'];
-        $this->assertEquals($expected, $listener->callStack);
+        $this->assertEquals($expected, $listener->callList);
         EventManager::instance(new EventManager());
     }
 
@@ -664,7 +665,7 @@ class EventManagerTest extends TestCase
         $manager->dispatch($event);
 
         $expected = ['listenerFunction', 'secondListenerFunction'];
-        $this->assertEquals($expected, $listener->callStack);
+        $this->assertEquals($expected, $listener->callList);
         EventManager::instance(new EventManager());
     }
 
@@ -696,7 +697,7 @@ class EventManagerTest extends TestCase
      * listeners at the same priority.
      *
      * @return void
-     * @triggers fake.event $this)
+     * @triggers fake.event $this
      */
     public function testDispatchWithGlobalAndLocalEvents()
     {
@@ -707,7 +708,43 @@ class EventManagerTest extends TestCase
         $manager->attach([$listener2, 'listenerFunction'], 'fake.event');
 
         $manager->dispatch(new Event('fake.event', $this));
-        $this->assertEquals(['listenerFunction'], $listener->callStack);
-        $this->assertEquals(['listenerFunction'], $listener2->callStack);
+        $this->assertEquals(['listenerFunction'], $listener->callList);
+        $this->assertEquals(['listenerFunction'], $listener2->callList);
+    }
+
+    /**
+     * Test getting a list of dispatched events from the manager.
+     *
+     * @return void
+     * @triggers my_event $this
+     * @triggers my_second_event $this
+     */
+    public function testGetDispatchedEvents()
+    {
+        $eventList = new EventList();
+        $event = new Event('my_event', $this);
+        $event2 = new Event('my_second_event', $this);
+
+        $manager = new EventManager();
+        $manager->setEventList($eventList);
+        $manager->dispatch($event);
+        $manager->dispatch($event2);
+
+        $result = $manager->getEventList();
+        $this->assertInstanceOf('\Cake\Event\EventList', $result);
+        $this->assertCount(2, $result);
+        $this->assertEquals($result[0], $event);
+        $this->assertEquals($result[1], $event2);
+
+        $manager->getEventList()->flush();
+        $result = $manager->getEventList();
+        $this->assertCount(0, $result);
+
+        $manager->unsetEventList();
+        $manager->dispatch($event);
+        $manager->dispatch($event2);
+
+        $result = $manager->getEventList();
+        $this->assertNull($result);
     }
 }