diff --git a/src/Composer/EventDispatcher/EventDispatcher.php b/src/Composer/EventDispatcher/EventDispatcher.php index c37d7cf45..c24660659 100644 --- a/src/Composer/EventDispatcher/EventDispatcher.php +++ b/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 * diff --git a/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php b/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php index 6d812e20a..a41d745ff 100644 --- a/tests/Composer/Test/EventDispatcher/EventDispatcherTest.php +++ b/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;