Browse Source

Add EventDispatcher::removeListener to allow deregistration of listeners

Jordi Boggiano 6 years ago
parent
commit
1b7e957cc1

+ 20 - 1
src/Composer/EventDispatcher/EventDispatcher.php

@@ -46,7 +46,7 @@ class EventDispatcher
     protected $io;
     protected $loader;
     protected $process;
-    protected $listeners;
+    protected $listeners = array();
     private $eventStack;
 
     /**
@@ -172,6 +172,9 @@ class EventDispatcher
 
                     throw new \RuntimeException('Subscriber '.$className.'::'.$callable[1].' for event '.$event->getName().' is not callable, make sure the function is defined and public');
                 }
+                if (is_array($callable) && (is_string($callable[0]) || is_object($callable[0])) && is_string($callable[1])) {
+                    $this->io->writeError(sprintf('> %s: %s', $event->getName(), (is_object($callable[0]) ? get_class($callable[0]) : $callable[0]).'->'.$callable[1] ), true, IOInterface::VERBOSE);
+                }
                 $event = $this->checkListenerExpectedEvent($callable, $event);
                 $return = false === call_user_func($callable, $event) ? 1 : 0;
             } elseif ($this->isComposerScript($callable)) {
@@ -364,6 +367,22 @@ class EventDispatcher
         $this->listeners[$eventName][$priority][] = $listener;
     }
 
+    /**
+     * @param callable|object $listener A callable or an object instance for which all listeners should be removed
+     */
+    public function removeListener($listener)
+    {
+        foreach ($this->listeners as $eventName => $priorities) {
+            foreach ($priorities as $priority => $listeners) {
+                foreach ($listeners as $index => $candidate) {
+                    if ($listener === $candidate || (is_array($candidate) && is_object($listener) && $candidate[0] === $listener)) {
+                        unset($this->listeners[$eventName][$priority][$index]);
+                    }
+                }
+            }
+        }
+    }
+
     /**
      * Adds object methods as listeners for the events in getSubscribedEvents
      *

+ 48 - 0
tests/Composer/Test/EventDispatcher/EventDispatcherTest.php

@@ -171,6 +171,49 @@ class EventDispatcherTest extends TestCase
         return $rm;
     }
 
+    public function testDispatcherRemoveListener()
+    {
+        $composer = $this->createComposerInstance();
+
+        $composer->setRepositoryManager($this->getRepositoryManagerMockForDevModePassingTest());
+        $composer->setInstallationManager($this->getMockBuilder('Composer\Installer\InstallationManager')->disableOriginalConstructor()->getMock());
+
+        $dispatcher = new EventDispatcher(
+            $composer,
+            $io = new BufferIO('', OutputInterface::VERBOSITY_VERBOSE),
+            $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock()
+        );
+
+        $listener = array($this, 'someMethod');
+        $listener2 = array($this, 'someMethod2');
+        $listener3 = 'Composer\\Test\\EventDispatcher\\EventDispatcherTest::someMethod';
+
+        $dispatcher->addListener('ev1', $listener, 0);
+        $dispatcher->addListener('ev1', $listener, 1);
+        $dispatcher->addListener('ev1', $listener2, 1);
+        $dispatcher->addListener('ev1', $listener3);
+        $dispatcher->addListener('ev2', $listener3);
+        $dispatcher->addListener('ev2', $listener);
+        $dispatcher->dispatch('ev1');
+        $dispatcher->dispatch('ev2');
+
+        $expected = '> ev1: Composer\Test\EventDispatcher\EventDispatcherTest->someMethod'.PHP_EOL
+            .'> ev1: Composer\Test\EventDispatcher\EventDispatcherTest->someMethod2'.PHP_EOL
+            .'> ev1: Composer\Test\EventDispatcher\EventDispatcherTest->someMethod'.PHP_EOL
+            .'> ev1: Composer\Test\EventDispatcher\EventDispatcherTest::someMethod'.PHP_EOL
+            .'> ev2: Composer\Test\EventDispatcher\EventDispatcherTest::someMethod'.PHP_EOL
+            .'> ev2: Composer\Test\EventDispatcher\EventDispatcherTest->someMethod'.PHP_EOL;
+        $this->assertEquals($expected, $io->getOutput());
+
+        $dispatcher->removeListener($this);
+        $dispatcher->dispatch('ev1');
+        $dispatcher->dispatch('ev2');
+
+        $expected .= '> ev1: Composer\Test\EventDispatcher\EventDispatcherTest::someMethod'.PHP_EOL
+            .'> ev2: Composer\Test\EventDispatcher\EventDispatcherTest::someMethod'.PHP_EOL;
+        $this->assertEquals($expected, $io->getOutput());
+    }
+
     public function testDispatcherCanExecuteCliAndPhpInSameEventScriptStack()
     {
         $process = $this->getMockBuilder('Composer\Util\ProcessExecutor')->getMock();
@@ -446,6 +489,11 @@ class EventDispatcherTest extends TestCase
         return true;
     }
 
+    public static function someMethod2()
+    {
+        return true;
+    }
+
     private function createComposerInstance()
     {
         $composer = new Composer;