Pārlūkot izejas kodu

[EventDispatcher] Replaced EventDispatcher by Doctrine's implementation

Bernhard Schussek 14 gadi atpakaļ
vecāks
revīzija
699e046b4f

+ 44 - 108
src/Symfony/Component/EventDispatcher/Event.php

@@ -1,136 +1,72 @@
 <?php
 
 /*
- * This file is part of the Symfony package.
+ *  $Id$
  *
- * (c) Fabien Potencier <fabien@symfony.com>
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
  */
 
 namespace Symfony\Component\EventDispatcher;
 
 /**
- * Event.
+ * Event is the base class for classes containing event data.
  *
- * @author Fabien Potencier <fabien@symfony.com>
+ * This class contains no event data. It is used by events that do not pass
+ * state information to an event handler when an event is raised.
+ *
+ * You can call the method stopPropagation() to abort the execution of
+ * further listeners in your event listener.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link    www.doctrine-project.org
+ * @since   2.0
+ * @version $Revision: 3938 $
+ * @author  Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author  Jonathan Wage <jonwage@gmail.com>
+ * @author  Roman Borschel <roman@code-factory.org>
+ * @author  Bernhard Schussek <bschussek@gmail.com>
  */
-class Event implements EventInterface
+class Event
 {
-    protected $processed = false;
-    protected $subject;
-    protected $name;
-    protected $parameters;
-
-    /**
-     * Constructs a new Event.
-     *
-     * @param mixed   $subject      The subject
-     * @param string  $name         The event name
-     * @param array   $parameters   An array of parameters
-     */
-    public function __construct($subject, $name, $parameters = array())
-    {
-        $this->subject = $subject;
-        $this->name = $name;
-        $this->parameters = $parameters;
-    }
-
-    /**
-     * Returns the subject.
-     *
-     * @return mixed The subject
-     */
-    public function getSubject()
-    {
-        return $this->subject;
-    }
-
     /**
-     * Returns the event name.
-     *
-     * @return string The event name
+     * @var Boolean Whether no further event listeners should be triggered
      */
-    public function getName()
-    {
-        return $this->name;
-    }
+    private $propagationStopped = false;
 
     /**
-     * Sets the processed flag to true.
+     * Returns whether further event listeners should be triggered.
      *
-     * This method must be called by listeners when
-     * it has processed the event (it is only meaningful
-     * when the event has been notified with the notifyUntil()
-     * dispatcher method.
+     * @see Event::stopPropagation
+     * @return Boolean Whether propagation was already stopped for this event.
      */
-    public function setProcessed()
+    public function isPropagationStopped()
     {
-        $this->processed = true;
-    }
-
-    /**
-     * Returns whether the event has been processed by a listener or not.
-     *
-     * This method is only meaningful for events notified
-     * with notifyUntil().
-     *
-     * @return Boolean true if the event has been processed, false otherwise
-     */
-    public function isProcessed()
-    {
-        return $this->processed;
-    }
-
-    /**
-     * Returns the event parameters.
-     *
-     * @return array The event parameters
-     */
-    public function all()
-    {
-        return $this->parameters;
-    }
-
-    /**
-     * Returns true if the parameter exists.
-     *
-     * @param  string  $name  The parameter name
-     *
-     * @return Boolean true if the parameter exists, false otherwise
-     */
-    public function has($name)
-    {
-        return array_key_exists($name, $this->parameters);
-    }
-
-    /**
-     * Returns a parameter value.
-     *
-     * @param  string  $name  The parameter name
-     *
-     * @return mixed  The parameter value
-     *
-     * @throws \InvalidArgumentException When parameter doesn't exists for this event
-     */
-    public function get($name)
-    {
-        if (!array_key_exists($name, $this->parameters)) {
-            throw new \InvalidArgumentException(sprintf('The event "%s" has no "%s" parameter.', $this->name, $name));
-        }
-
-        return $this->parameters[$name];
+        return $this->propagationStopped;
     }
 
     /**
-     * Sets a parameter.
+     * Stops the propagation of the event to further event listeners.
      *
-     * @param string  $name   The parameter name
-     * @param mixed   $value  The parameter value
+     * If multiple event listeners are connected to the same event, no
+     * further event listener will be triggered once any trigger calls
+     * stopPropagation().
      */
-    public function set($name, $value)
+    public function stopPropagation()
     {
-        $this->parameters[$name] = $value;
+        $this->propagationStopped = true;
     }
 }

+ 137 - 82
src/Symfony/Component/EventDispatcher/EventDispatcher.php

@@ -1,147 +1,202 @@
 <?php
 
 /*
- * This file is part of the Symfony package.
+ *  $Id$
  *
- * (c) Fabien Potencier <fabien@symfony.com>
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
  */
 
 namespace Symfony\Component\EventDispatcher;
 
 /**
- * EventDispatcher implements a dispatcher object.
+ * The EventManager is the central point of Doctrine's event listener system.
+ * Listeners are registered on the manager and events are dispatched through the
+ * manager.
  *
- * @author Fabien Potencier <fabien@symfony.com>
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link    www.doctrine-project.org
+ * @since   2.0
+ * @version $Revision: 3938 $
+ * @author  Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author  Jonathan Wage <jonwage@gmail.com>
+ * @author  Roman Borschel <roman@code-factory.org>
+ * @author  Bernhard Schussek <bschussek@gmail.com>
  */
 class EventDispatcher implements EventDispatcherInterface
 {
-    protected $listeners = array();
+    /**
+     * Map of registered listeners.
+     * <event> => (<objecthash> => <listener>)
+     *
+     * @var array
+     */
+    private $listeners = array();
 
     /**
-     * Connects a listener to a given event name.
+     * Map of priorities by the object hashes of their listeners.
+     * <event> => (<objecthash> => <priority>)
      *
-     * Listeners with a higher priority are executed first.
+     * This property is used for listener sorting.
      *
-     * @param string  $name      An event name
-     * @param mixed   $listener  A PHP callable
-     * @param integer $priority  The priority (between -10 and 10 -- defaults to 0)
+     * @var array
      */
-    public function connect($name, $listener, $priority = 0)
-    {
-        if (!isset($this->listeners[$name][$priority])) {
-            if (!isset($this->listeners[$name])) {
-                $this->listeners[$name] = array();
-            }
-            $this->listeners[$name][$priority] = array();
-        }
-
-        $this->listeners[$name][$priority][] = $listener;
-    }
+    private $priorities = array();
 
     /**
-     * Disconnects one, or all listeners for the given event name.
-     *
-     * @param string     $name     An event name
-     * @param mixed|null $listener The listener to remove, or null to remove all
+     * Stores which event listener lists are currently sorted.
+     * <event> => <sorted>
      *
-     * @return void
+     * @var array
      */
-    public function disconnect($name, $listener = null)
+    private $sorted = array();
+
+    /**
+     * @see EventDispatcherInterface::dispatchEvent
+     */
+    public function dispatchEvent($eventName, Event $event = null)
     {
-        if (!isset($this->listeners[$name])) {
-            return;
-        }
+        if (isset($this->listeners[$eventName])) {
+            if (null === $event) {
+                $event = new Event();
+            }
 
-        if (null === $listener) {
-            unset($this->listeners[$name]);
-            return;
-        }
+            $this->sortListeners($eventName);
+
+            foreach ($this->listeners[$eventName] as $listener) {
+                $this->triggerListener($listener, $eventName, $event);
 
-        foreach ($this->listeners[$name] as $priority => $callables) {
-            foreach ($callables as $i => $callable) {
-                if ($listener === $callable) {
-                    unset($this->listeners[$name][$priority][$i]);
+                if ($event->isPropagationStopped()) {
+                    break;
                 }
             }
         }
     }
 
     /**
-     * Notifies all listeners of a given event.
+     * Triggers the listener method for an event.
      *
-     * @param EventInterface $event An EventInterface instance
+     * This method can be overridden to add functionality that is executed
+     * for each listener.
+     *
+     * @param object $listener The event listener on which to invoke the listener method.
+     * @param string $eventName The name of the event to dispatch. The name of the event is
+     *                          the name of the method that is invoked on listeners.
+     * @param Event $event The event arguments to pass to the event handlers/listeners.
      */
-    public function notify(EventInterface $event)
+    protected function triggerListener($listener, $eventName, Event $event)
     {
-        foreach ($this->getListeners($event->getName()) as $listener) {
-            call_user_func($listener, $event);
+        if ($listener instanceof \Closure) {
+            $listener->__invoke($event);
+        } else {
+            $listener->$eventName($event);
         }
     }
 
     /**
-     * Notifies all listeners of a given event until one processes the event.
+     * Sorts the internal list of listeners for the given event by priority.
      *
-     * @param  EventInterface $event An EventInterface instance
+     * Calling this method multiple times will not cause overhead unless you
+     * add new listeners. As long as no listener is added, the list for an
+     * event name won't be sorted twice.
      *
-     * @return mixed The returned value of the listener that processed the event
+     * @param string $event The name of the event.
      */
-    public function notifyUntil(EventInterface $event)
+    private function sortListeners($eventName)
     {
-        foreach ($this->getListeners($event->getName()) as $listener) {
-            $ret = call_user_func($listener, $event);
-            if ($event->isProcessed()) {
-                return $ret;
-            }
+        if (!$this->sorted[$eventName]) {
+            $p = $this->priorities[$eventName];
+
+            uasort($this->listeners[$eventName], function ($a, $b) use ($p) {
+                return $p[spl_object_hash($b)] - $p[spl_object_hash($a)];
+            });
+
+            $this->sorted[$eventName] = true;
         }
     }
 
     /**
-     * Filters a value by calling all listeners of a given event.
-     *
-     * @param  EventInterface $event An EventInterface instance
-     * @param  mixed          $value The value to be filtered
-     *
-     * @return mixed The filtered value
+     * @see EventDispatcherInterface::getListeners
      */
-    public function filter(EventInterface $event, $value)
+    public function getListeners($eventName = null)
     {
-        foreach ($this->getListeners($event->getName()) as $listener) {
-            $value = call_user_func($listener, $event, $value);
+        if ($eventName) {
+            $this->sortListeners($eventName);
+
+            return $this->listeners[$eventName];
+        }
+
+        foreach ($this->listeners as $eventName => $listeners) {
+            $this->sortListeners($eventName);
         }
 
-        return $value;
+        return $this->listeners;
     }
 
     /**
-     * Returns true if the given event name has some listeners.
-     *
-     * @param  string $name The event name
-     *
-     * @return Boolean true if some listeners are connected, false otherwise
+     * @see EventDispatcherInterface::hasListeners
      */
-    public function hasListeners($name)
+    public function hasListeners($eventName)
     {
-        return (Boolean) count($this->getListeners($name));
+        return isset($this->listeners[$eventName]) && $this->listeners[$eventName];
     }
 
     /**
-     * Returns all listeners associated with a given event name.
-     *
-     * @param  string $name The event name
-     *
-     * @return array  An array of listeners
+     * @see EventDispatcherInterface::addEventListener
      */
-    public function getListeners($name)
+    public function addEventListener($eventNames, $listener, $priority = 0)
     {
-        if (!isset($this->listeners[$name])) {
-            return array();
+        // Picks the hash code related to that listener
+        $hash = spl_object_hash($listener);
+
+        foreach ((array) $eventNames as $eventName) {
+            if (!isset($this->listeners[$eventName])) {
+                $this->listeners[$eventName] = array();
+                $this->priorities[$eventName] = array();
+            }
+
+            // Prevents duplicate listeners on same event (same instance only)
+            $this->listeners[$eventName][$hash] = $listener;
+            $this->priorities[$eventName][$hash] = $priority;
+            $this->sorted[$eventName] = false;
         }
+    }
 
-        krsort($this->listeners[$name]);
+    /**
+     * @see EventDispatcherInterface::removeEventListener
+     */
+    public function removeEventListener($eventNames, $listener)
+    {
+        // Picks the hash code related to that listener
+        $hash = spl_object_hash($listener);
+
+        foreach ((array) $eventNames as $eventName) {
+            // Check if actually have this listener associated
+            if (isset($this->listeners[$eventName][$hash])) {
+                unset($this->listeners[$eventName][$hash]);
+                unset($this->priorities[$eventName][$hash]);
+            }
+        }
+    }
 
-        return call_user_func_array('array_merge', $this->listeners[$name]);
+    /**
+     * @see EventDispatcherInterface::addEventSubscriber
+     */
+    public function addEventSubscriber(EventSubscriberInterface $subscriber, $priority = 0)
+    {
+        $this->addEventListener($subscriber->getSubscribedEvents(), $subscriber, $priority);
     }
-}
+}

+ 40 - 49
src/Symfony/Component/EventDispatcher/EventDispatcherInterface.php

@@ -12,81 +12,72 @@
 namespace Symfony\Component\EventDispatcher;
 
 /**
- * EventDispatcherInterface describes an event dispatcher class.
+ * The EventManager is the central point of Doctrine's event listener system.
+ * Listeners are registered on the manager and events are dispatched through the
+ * manager.
  *
- * @see http://developer.apple.com/documentation/Cocoa/Conceptual/Notifications/index.html Apple's Cocoa framework
- *
- * @author Fabien Potencier <fabien@symfony.com>
+ * @author Bernhard Schussek <bschussek@gmail.com>
  */
 interface EventDispatcherInterface
 {
     /**
-     * Connects a listener to a given event name.
-     *
-     * Listeners with a higher priority are executed first.
+     * Adds an event listener that listens on the specified events.
      *
-     * @param string  $name      An event name
-     * @param mixed   $listener  A PHP callable
-     * @param integer $priority  The priority (between -10 and 10 -- defaults to 0)
+     * @param string|array $eventNames The event(s) to listen on.
+     * @param object $listener The listener object.
+     * @param integer $priority The higher this value, the earlier an event
+     *                          listener will be triggered in the chain.
+     *                          Defaults to 0.
      */
-    function connect($name, $listener, $priority = 0);
+    function addEventListener($eventNames, $listener, $priority = 0);
 
     /**
-     * Disconnects one, or all listeners for the given event name.
-     *
-     * @param string     $name     An event name
-     * @param mixed|null $listener The listener to remove, or null to remove all
+     * Adds an event subscriber. The subscriber is asked for all the events he is
+     * interested in and added as a listener for these events.
      *
-     * @return void
+     * @param EventSubscriberInterface $subscriber The subscriber.
+     * @param integer $priority The higher this value, the earlier an event
+     *                          listener will be triggered in the chain.
+     *                          Defaults to 0.
      */
-    function disconnect($name, $listener = null);
+    function addEventSubscriber(EventSubscriberInterface $subscriber, $priority = 0);
 
     /**
-     * Notifies all listeners of a given event.
+     * Removes an event listener from the specified events.
      *
-     * @param EventInterface $event An EventInterface instance
+     * @param string|array $eventNames The event(s) to remove a listener from.
+     * @param object $listener The listener object to remove.
      */
-    function notify(EventInterface $event);
+    function removeEventListener($eventNames, $listener);
 
     /**
-     * Notifies all listeners of a given event until one processes the event.
-     *
-     * A listener tells the dispatcher that it has processed the event
-     * by calling the setProcessed() method on it.
-     *
-     * It can then return a value that will be forwarded to the caller.
-     *
-     * @param  EventInterface $event An EventInterface instance
-     *
-     * @return mixed The returned value of the listener that processed the event
-     */
-    function notifyUntil(EventInterface $event);
-
-    /**
-     * Filters a value by calling all listeners of a given event.
-     *
-     * @param  EventInterface $event An EventInterface instance
-     * @param  mixed          $value The value to be filtered
+     * Dispatches an event to all registered listeners.
      *
-     * @return mixed The filtered value
+     * @param string $eventName The name of the event to dispatch. The name of
+     *                          the event is the name of the method that is
+     *                          invoked on listeners.
+     * @param Event $event The event to pass to the event handlers/listeners.
+     *                     If not supplied, an empty Event instance is created.
      */
-    function filter(EventInterface $event, $value);
+    function dispatchEvent($eventName, Event $event = null);
 
     /**
-     * Returns true if the given event name has some listeners.
+     * Gets the listeners of a specific event or all listeners.
      *
-     * @param  string $name The event name
+     * @param string $eventName The name of the event.
      *
-     * @return Boolean true if some listeners are connected, false otherwise
+     * @return array The event listeners for the specified event, or all event
+     *               listeners by event name.
      */
-    function hasListeners($name);
+    function getListeners($eventName = null);
 
     /**
-     * Returns all listeners associated with a given event name.
+     * Checks whether an event has any registered listeners.
      *
-     * @param  string $name The event name
+     * @param string $eventName The name of the event.
      *
-     * @return array  An array of listeners
+     * @return Boolean TRUE if the specified event has any listeners, FALSE
+     *                 otherwise.
      */
-    function getListeners($name);
-}
+    function hasListeners($eventName);
+}

+ 0 - 89
src/Symfony/Component/EventDispatcher/EventInterface.php

@@ -1,89 +0,0 @@
-<?php
-
-/*
- * This file is part of the Symfony package.
- *
- * (c) Fabien Potencier <fabien@symfony.com>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\Component\EventDispatcher;
-
-/**
- * EventInterface.
- *
- * @author Fabien Potencier <fabien@symfony.com>
- */
-interface EventInterface
-{
-    /**
-     * Returns the subject.
-     *
-     * @return mixed The subject
-     */
-    function getSubject();
-
-    /**
-     * Returns the event name.
-     *
-     * @return string The event name
-     */
-    function getName();
-
-    /**
-     * Sets the processed flag to true.
-     *
-     * This method must be called by listeners when
-     * it has processed the event (it is only meaningful
-     * when the event has been notified with the notifyUntil()
-     * dispatcher method.
-     */
-    function setProcessed();
-
-    /**
-     * Returns whether the event has been processed by a listener or not.
-     *
-     * This method is only meaningful for events notified
-     * with notifyUntil().
-     *
-     * @return Boolean true if the event has been processed, false otherwise
-     */
-    function isProcessed();
-
-    /**
-     * Returns the event parameters.
-     *
-     * @return array The event parameters
-     */
-    function all();
-
-    /**
-     * Returns true if the parameter exists.
-     *
-     * @param  string  $name  The parameter name
-     *
-     * @return Boolean true if the parameter exists, false otherwise
-     */
-    function has($name);
-
-    /**
-     * Returns a parameter value.
-     *
-     * @param  string  $name  The parameter name
-     *
-     * @return mixed  The parameter value
-     *
-     * @throws \InvalidArgumentException When parameter doesn't exists for this event
-     */
-    function get($name);
-
-    /**
-     * Sets a parameter.
-     *
-     * @param string  $name   The parameter name
-     * @param mixed   $value  The parameter value
-     */
-    function set($name, $value);
-}

+ 47 - 0
src/Symfony/Component/EventDispatcher/EventSubscriberInterface.php

@@ -0,0 +1,47 @@
+<?php
+
+/*
+ *  $Id: EventListener.php 4653 2008-07-10 17:17:58Z romanb $
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * This software consists of voluntary contributions made by many individuals
+ * and is licensed under the LGPL. For more information, see
+ * <http://www.doctrine-project.org>.
+ */
+
+namespace Symfony\Component\EventDispatcher;
+
+/**
+ * An EventSubscriber knows himself what events he is interested in.
+ * If an EventSubscriber is added to an EventManager, the manager invokes
+ * {@link getSubscribedEvents} and registers the subscriber as a listener for all
+ * returned events.
+ *
+ * @license http://www.opensource.org/licenses/lgpl-license.php LGPL
+ * @link    www.doctrine-project.org
+ * @since   2.0
+ * @author  Guilherme Blanco <guilhermeblanco@hotmail.com>
+ * @author  Jonathan Wage <jonwage@gmail.com>
+ * @author  Roman Borschel <roman@code-factory.org>
+ * @author  Bernhard Schussek <bschussek@gmail.com>
+ */
+interface EventSubscriberInterface
+{
+    /**
+     * Returns an array of events this subscriber wants to listen to.
+     *
+     * @return array
+     */
+    public static function getSubscribedEvents();
+}

+ 143 - 82
tests/Symfony/Tests/Component/EventDispatcher/EventDispatcherTest.php

@@ -13,126 +13,187 @@ namespace Symfony\Tests\Component\EventDispatcher;
 
 use Symfony\Component\EventDispatcher\Event;
 use Symfony\Component\EventDispatcher\EventDispatcher;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
 
 class EventDispatcherTest extends \PHPUnit_Framework_TestCase
 {
-    public function testConnectAndDisconnect()
-    {
-        $dispatcher = new EventDispatcher();
+    /* Some pseudo events */
+    const preFoo = 'preFoo';
+    const postFoo = 'postFoo';
+    const preBar = 'preBar';
+    const postBar = 'postBar';
 
-        $dispatcher->connect('bar', 'listenToBar');
-        $this->assertEquals(array('listenToBar'), $dispatcher->getListeners('bar'), '->connect() connects a listener to an event name');
-        $dispatcher->connect('bar', 'listenToBarBar');
-        $this->assertEquals(array('listenToBar', 'listenToBarBar'), $dispatcher->getListeners('bar'), '->connect() can connect several listeners for the same event name');
+    private $dispatcher;
 
-        $dispatcher->connect('barbar', 'listenToBarBar');
+    private $listener;
 
-        $dispatcher->disconnect('bar');
-        $this->assertEquals(array(), $dispatcher->getListeners('bar'), '->disconnect() without a listener disconnects all listeners of for an event name');
-        $this->assertEquals(array('listenToBarBar'), $dispatcher->getListeners('barbar'), '->disconnect() without a listener disconnects all listeners of for an event name');
+    protected function setUp()
+    {
+        $this->dispatcher = new EventDispatcher();
+        $this->listener = new TestEventListener();
     }
 
-    public function testGetHasListeners()
+    public function testInitialState()
     {
-        $dispatcher = new EventDispatcher();
-
-        $this->assertFalse($dispatcher->hasListeners('foo'), '->hasListeners() returns false if the event has no listener');
-        $dispatcher->connect('foo', 'listenToFoo');
-        $this->assertEquals(true, $dispatcher->hasListeners('foo'), '->hasListeners() returns true if the event has some listeners');
-        $dispatcher->disconnect('foo', 'listenToFoo');
-        $this->assertFalse($dispatcher->hasListeners('foo'), '->hasListeners() returns false if the event has no listener');
+        $this->assertEquals(array(), $this->dispatcher->getListeners());
+        $this->assertFalse($this->dispatcher->hasListeners(self::preFoo));
+        $this->assertFalse($this->dispatcher->hasListeners(self::postFoo));
+    }
 
-        $dispatcher->connect('bar', 'listenToBar');
-        $this->assertEquals(array('listenToBar'), $dispatcher->getListeners('bar'), '->getListeners() returns an array of listeners connected to the given event name');
-        $this->assertEquals(array(), $dispatcher->getListeners('foobar'), '->getListeners() returns an empty array if no listener are connected to the given event name');
+    public function testAddEventListener()
+    {
+        $this->dispatcher->addEventListener(array('preFoo', 'postFoo'), $this->listener);
+        $this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
+        $this->assertTrue($this->dispatcher->hasListeners(self::postFoo));
+        $this->assertEquals(1, count($this->dispatcher->getListeners(self::preFoo)));
+        $this->assertEquals(1, count($this->dispatcher->getListeners(self::postFoo)));
+        $this->assertEquals(2, count($this->dispatcher->getListeners()));
     }
 
-    public function testNotify()
+    public function testGetListenersSortsByPriority()
     {
-        $listener = new Listener();
-        $dispatcher = new EventDispatcher();
-        $dispatcher->connect('foo', array($listener, 'listenToFoo'));
-        $dispatcher->connect('foo', array($listener, 'listenToFooBis'));
-        $e = $dispatcher->notify($event = new Event(new \stdClass(), 'foo'));
-        $this->assertEquals('listenToFoolistenToFooBis', $listener->getValue(), '->notify() notifies all registered listeners in order');
-
-        $listener->reset();
-        $dispatcher = new EventDispatcher();
-        $dispatcher->connect('foo', array($listener, 'listenToFooBis'));
-        $dispatcher->connect('foo', array($listener, 'listenToFoo'));
-        $dispatcher->notify(new Event(new \stdClass(), 'foo'));
-        $this->assertEquals('listenToFooBislistenToFoo', $listener->getValue(), '->notify() notifies all registered listeners in order');
+        $listener1 = new TestEventListener();
+        $listener2 = new TestEventListener();
+        $listener3 = new TestEventListener();
+
+        $this->dispatcher->addEventListener('preFoo', $listener1, -10);
+        $this->dispatcher->addEventListener('preFoo', $listener2);
+        $this->dispatcher->addEventListener('preFoo', $listener3, 10);
+
+        $expected = array(
+            spl_object_hash($listener3) => $listener3,
+            spl_object_hash($listener2) => $listener2,
+            spl_object_hash($listener1) => $listener1,
+        );
+
+        $this->assertSame($expected, $this->dispatcher->getListeners('preFoo'));
     }
 
-    public function testNotifyUntil()
+    public function testGetAllListenersSortsByPriority()
     {
-        $listener = new Listener();
-        $dispatcher = new EventDispatcher();
-        $dispatcher->connect('foo', array($listener, 'listenToFoo'));
-        $dispatcher->connect('foo', array($listener, 'listenToFooBis'));
-        $dispatcher->notifyUntil($event = new Event(new \stdClass(), 'foo'));
-        $this->assertEquals('listenToFoolistenToFooBis', $listener->getValue(), '->notifyUntil() notifies all registered listeners in order and stops when the event is processed');
-
-        $listener->reset();
-        $dispatcher = new EventDispatcher();
-        $dispatcher->connect('foo', array($listener, 'listenToFooBis'));
-        $dispatcher->connect('foo', array($listener, 'listenToFoo'));
-        $dispatcher->notifyUntil($event = new Event(new \stdClass(), 'foo'));
-        $this->assertEquals('listenToFooBis', $listener->getValue(), '->notifyUntil() notifies all registered listeners in order and stops when the event is processed');
+        $listener1 = new TestEventListener();
+        $listener2 = new TestEventListener();
+        $listener3 = new TestEventListener();
+        $listener4 = new TestEventListener();
+        $listener5 = new TestEventListener();
+        $listener6 = new TestEventListener();
+
+        $this->dispatcher->addEventListener('preFoo', $listener1, -10);
+        $this->dispatcher->addEventListener('preFoo', $listener2);
+        $this->dispatcher->addEventListener('preFoo', $listener3, 10);
+        $this->dispatcher->addEventListener('postFoo', $listener4, -10);
+        $this->dispatcher->addEventListener('postFoo', $listener5);
+        $this->dispatcher->addEventListener('postFoo', $listener6, 10);
+
+        $expected = array(
+            'preFoo' => array(
+                spl_object_hash($listener3) => $listener3,
+                spl_object_hash($listener2) => $listener2,
+                spl_object_hash($listener1) => $listener1,
+             ),
+            'postFoo' => array(
+                spl_object_hash($listener6) => $listener6,
+                spl_object_hash($listener5) => $listener5,
+                spl_object_hash($listener4) => $listener4,
+             ),
+        );
+
+        $this->assertSame($expected, $this->dispatcher->getListeners());
     }
 
-    public function testFilter()
+    public function testDispatchEvent()
     {
-        $listener = new Listener();
-        $dispatcher = new EventDispatcher();
-        $dispatcher->connect('foo', array($listener, 'filterFoo'));
-        $dispatcher->connect('foo', array($listener, 'filterFooBis'));
-        $ret = $dispatcher->filter($event = new Event(new \stdClass(), 'foo'), 'foo');
-        $this->assertEquals('-*foo*-', $ret, '->filter() returns the filtered value');
-
-        $listener->reset();
-        $dispatcher = new EventDispatcher();
-        $dispatcher->connect('foo', array($listener, 'filterFooBis'));
-        $dispatcher->connect('foo', array($listener, 'filterFoo'));
-        $ret = $dispatcher->filter($event = new Event(new \stdClass(), 'foo'), 'foo');
-        $this->assertEquals('*-foo-*', $ret, '->filter() returns the filtered value');
+        $this->dispatcher->addEventListener(array('preFoo', 'postFoo'), $this->listener);
+        $this->dispatcher->dispatchEvent(self::preFoo);
+        $this->assertTrue($this->listener->preFooInvoked);
+        $this->assertFalse($this->listener->postFooInvoked);
     }
-}
 
-class Listener
-{
-    protected
-        $value = '';
+    public function testDispatchEventForClosure()
+    {
+        $invoked = 0;
+        $listener = function () use (&$invoked) {
+            $invoked++;
+        };
+        $this->dispatcher->addEventListener(array('preFoo', 'postFoo'), $listener);
+        $this->dispatcher->dispatchEvent(self::preFoo);
+        $this->assertEquals(1, $invoked);
+    }
 
-    function filterFoo(Event $event, $foo)
+    public function testStopEventPropagation()
     {
-        return "*$foo*";
+        $otherListener = new TestEventListener;
+
+        // postFoo() stops the propagation, so only one listener should
+        // be executed
+        // Manually set priority to enforce $this->listener to be called first
+        $this->dispatcher->addEventListener('postFoo', $this->listener, 10);
+        $this->dispatcher->addEventListener('postFoo', $otherListener);
+        $this->dispatcher->dispatchEvent(self::postFoo);
+        $this->assertTrue($this->listener->postFooInvoked);
+        $this->assertFalse($otherListener->postFooInvoked);
     }
 
-    function filterFooBis(Event $event, $foo)
+    public function testDispatchByPriority()
     {
-        return "-$foo-";
+        $invoked = array();
+        $listener1 = function () use (&$invoked) {
+            $invoked[] = '1';
+        };
+        $listener2 = function () use (&$invoked) {
+            $invoked[] = '2';
+        };
+        $listener3 = function () use (&$invoked) {
+            $invoked[] = '3';
+        };
+        $this->dispatcher->addEventListener('preFoo', $listener1, -10);
+        $this->dispatcher->addEventListener('preFoo', $listener2);
+        $this->dispatcher->addEventListener('preFoo', $listener3, 10);
+        $this->dispatcher->dispatchEvent(self::preFoo);
+        $this->assertEquals(array('3', '2', '1'), $invoked);
     }
 
-    function listenToFoo(Event $event)
+    public function testRemoveEventListener()
     {
-        $this->value .= 'listenToFoo';
+        $this->dispatcher->addEventListener(array('preBar'), $this->listener);
+        $this->assertTrue($this->dispatcher->hasListeners(self::preBar));
+        $this->dispatcher->removeEventListener(array('preBar'), $this->listener);
+        $this->assertFalse($this->dispatcher->hasListeners(self::preBar));
     }
 
-    function listenToFooBis(Event $event)
+    public function testAddEventSubscriber()
     {
-        $this->value .= 'listenToFooBis';
+        $eventSubscriber = new TestEventSubscriber();
+        $this->dispatcher->addEventSubscriber($eventSubscriber);
+        $this->assertTrue($this->dispatcher->hasListeners(self::preFoo));
+        $this->assertTrue($this->dispatcher->hasListeners(self::postFoo));
+    }
+}
+
+class TestEventListener
+{
+    public $preFooInvoked = false;
+    public $postFooInvoked = false;
 
-        $event->setProcessed();
+    /* Listener methods */
+
+    public function preFoo(Event $e)
+    {
+        $this->preFooInvoked = true;
     }
 
-    function getValue()
+    public function postFoo(Event $e)
     {
-        return $this->value;
+        $this->postFooInvoked = true;
+
+        $e->stopPropagation();
     }
+}
 
-    function reset()
+class TestEventSubscriber implements EventSubscriberInterface
+{
+    public static function getSubscribedEvents()
     {
-        $this->value = '';
+        return array('preFoo', 'postFoo');
     }
 }

+ 0 - 68
tests/Symfony/Tests/Component/EventDispatcher/EventTest.php

@@ -1,68 +0,0 @@
-<?php
-
-/*
- * This file is part of the Symfony package.
- *
- * (c) Fabien Potencier <fabien@symfony.com>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\Tests\Component\EventDispatcher;
-
-use Symfony\Component\EventDispatcher\Event;
-
-class EventTest extends \PHPUnit_Framework_TestCase
-{
-    protected $subject;
-    protected $parameters;
-
-    public function testGetSubject()
-    {
-        $event = $this->createEvent();
-        $this->assertEquals($this->subject, $event->getSubject(), '->getSubject() returns the event subject');
-    }
-
-    public function testGetName()
-    {
-        $this->assertEquals('name', $this->createEvent()->getName(), '->getName() returns the event name');
-    }
-
-    public function testParameters()
-    {
-        $event = $this->createEvent();
-
-        $this->assertEquals($this->parameters, $event->all(), '->all() returns the event parameters');
-        $this->assertEquals('bar', $event->get('foo'), '->get() returns the value of a parameter');
-        $event->set('foo', 'foo');
-        $this->assertEquals('foo', $event->get('foo'), '->set() changes the value of a parameter');
-        $this->assertTrue($event->has('foo'), '->has() returns true if the parameter is defined');
-        $this->assertFalse($event->has('oof'), '->has() returns false if the parameter is not defined');
-
-        try {
-            $event->get('foobar');
-            $this->fail('->get() throws an \InvalidArgumentException exception when the parameter does not exist');
-        } catch (\Exception $e) {
-            $this->assertInstanceOf('\InvalidArgumentException', $e, '->get() throws an \InvalidArgumentException exception when the parameter does not exist');
-            $this->assertEquals('The event "name" has no "foobar" parameter.', $e->getMessage(), '->get() throws an \InvalidArgumentException exception when the parameter does not exist');
-        }
-        $event = new Event($this->subject, 'name', $this->parameters);
-    }
-
-    public function testSetIsProcessed()
-    {
-        $event = $this->createEvent();
-        $this->assertFalse($event->isProcessed(), '->isProcessed() returns false by default');
-        $event->setProcessed();
-        $this->assertTrue($event->isProcessed(), '->isProcessed() returns true if the event has been processed');
-    }
-
-    protected function createEvent()
-    {
-        $this->subject = new \stdClass();
-        $this->parameters = array('foo' => 'bar');
-
-        return new Event($this->subject, 'name', $this->parameters);
-    }
-}