diff --git a/src/Composer/Script/EventDispatcher.php b/src/Composer/Script/EventDispatcher.php index b4537ed62..6c6d26b87 100644 --- a/src/Composer/Script/EventDispatcher.php +++ b/src/Composer/Script/EventDispatcher.php @@ -16,6 +16,7 @@ use Composer\Autoload\AutoloadGenerator; use Composer\IO\IOInterface; use Composer\Composer; use Composer\DependencyResolver\Operation\OperationInterface; +use Composer\Util\ProcessExecutor; /** * The Event Dispatcher. @@ -34,6 +35,7 @@ class EventDispatcher protected $composer; protected $io; protected $loader; + protected $process; /** * Constructor. @@ -41,10 +43,11 @@ class EventDispatcher * @param Composer $composer The composer instance * @param IOInterface $io The IOInterface instance */ - public function __construct(Composer $composer, IOInterface $io) + public function __construct(Composer $composer, IOInterface $io, ProcessExecutor $process = null) { $this->composer = $composer; $this->io = $io; + $this->process = $process ?: new ProcessExecutor(); } /** @@ -78,24 +81,37 @@ class EventDispatcher $listeners = $this->getListeners($event); foreach ($listeners as $callable) { - $className = substr($callable, 0, strpos($callable, '::')); - $methodName = substr($callable, strpos($callable, '::') + 2); - - if (!class_exists($className)) { - $this->io->write('Class '.$className.' is not autoloadable, can not call '.$event->getName().' script'); - continue; - } - if (!is_callable($callable)) { - $this->io->write('Method '.$callable.' is not callable, can not call '.$event->getName().' script'); - continue; - } - - try { - $className::$methodName($event); - } catch (\Exception $e) { - $message = "Script %s handling the %s event terminated with an exception"; - $this->io->write(''.sprintf($message, $callable, $event->getName()).''); - throw $e; + if ($this->isPhpScript($callable)) { + $className = substr($callable, 0, strpos($callable, '::')); + $methodName = substr($callable, strpos($callable, '::') + 2); + + if (!class_exists($className)) { + $this->io->write('Class '.$className.' is not autoloadable, can not call '.$event->getName().' script'); + continue; + } + if (!is_callable($callable)) { + $this->io->write('Method '.$callable.' is not callable, can not call '.$event->getName().' script'); + continue; + } + + try { + $className::$methodName($event); + } catch (\Exception $e) { + $message = "Script %s handling the %s event terminated with an exception"; + $this->io->write(''.sprintf($message, $callable, $event->getName()).''); + throw $e; + } + } else { + $callback = function ($type, $buffer) use ($event, $callable) { + $io = $event->getIO(); + if ('err' === $type) { + $message = 'Script %s handling the %s event returned an error: %s'; + $io->write(sprintf(''.$message.'', $callable, $event->getName(), $buffer)); + } else { + $io->write($buffer, false); + } + }; + $this->process->execute($callable, $callback); } } } @@ -126,4 +142,15 @@ class EventDispatcher return $scripts[$event->getName()]; } + + /** + * Checks if string given references a class path and method + * + * @param string $callable + * @return boolean + */ + protected function isPhpScript($callable) + { + return false !== strpos($callable, '::'); + } } diff --git a/tests/Composer/Test/Script/EventDispatcherTest.php b/tests/Composer/Test/Script/EventDispatcherTest.php index e23dccf8a..682624d9f 100644 --- a/tests/Composer/Test/Script/EventDispatcherTest.php +++ b/tests/Composer/Test/Script/EventDispatcherTest.php @@ -35,6 +35,32 @@ class EventDispatcherTest extends TestCase $dispatcher->dispatchCommandEvent("post-install-cmd"); } + public function testDispatcherCanExecuteCommandLineScripts() + { + $eventCliCommand = 'phpunit'; + + $process = $this->getMock('Composer\Util\ProcessExecutor'); + $dispatcher = $this->getMockBuilder('Composer\Script\EventDispatcher') + ->setConstructorArgs(array( + $this->getMock('Composer\Composer'), + $this->getMock('Composer\IO\IOInterface'), + $process, + )) + ->setMethods(array('getListeners')) + ->getMock(); + + $listeners = array($eventCliCommand); + $dispatcher->expects($this->atLeastOnce()) + ->method('getListeners') + ->will($this->returnValue($listeners)); + + $process->expects($this->once()) + ->method('execute') + ->with($eventCliCommand); + + $dispatcher->dispatchCommandEvent("post-install-cmd"); + } + private function getDispatcherStubForListenersTest($listeners, $io) { $dispatcher = $this->getMockBuilder('Composer\Script\EventDispatcher')