diff --git a/src/Symfony/Component/Process/Process.php b/src/Symfony/Component/Process/Process.php index f5104164bff6..c7a1cf2a445b 100644 --- a/src/Symfony/Component/Process/Process.php +++ b/src/Symfony/Component/Process/Process.php @@ -54,6 +54,7 @@ class Process private $exitcode; private $fallbackExitcode; private $processInformation; + private $outputDisabled = false; private $stdout; private $stderr; private $enhanceWindowsCompatibility; @@ -193,6 +194,7 @@ public function __clone() * @return integer The exit status code * * @throws RuntimeException When process can't be launch or is stopped + * @throws LogicException In case a callback is provided and output has been disabled * * @api */ @@ -244,12 +246,16 @@ public function mustRun($callback = null) * * @throws RuntimeException When process can't be launch or is stopped * @throws RuntimeException When process is already running + * @throws LogicException In case a callback is provided and output has been disabled */ public function start($callback = null) { if ($this->isRunning()) { throw new RuntimeException('Process is already running'); } + if ($this->outputDisabled && null !== $callback) { + throw new LogicException('Output has been disabled, enable it to allow the use of a callback.'); + } $this->resetProcessData(); $this->starttime = $this->lastOutputTime = microtime(true); @@ -400,15 +406,67 @@ public function signal($signal) return $this; } + /** + * Disables fetching output and error output from the underlying process. + * + * @return Process + * + * @throws RuntimeException In case the process is already running + */ + public function disableOutput() + { + if ($this->isRunning()) { + throw new RuntimeException('Disabling output while the process is running is not possible.'); + } + + $this->outputDisabled = true; + + return $this; + } + + /** + * Enables fetching output and error output from the underlying process. + * + * @return Process + * + * @throws RuntimeException In case the process is already running + */ + public function enableOutput() + { + if ($this->isRunning()) { + throw new RuntimeException('Enabling output while the process is running is not possible.'); + } + + $this->outputDisabled = false; + + return $this; + } + + /** + * Returns true in case the output is disabled, false otherwise. + * + * @return Boolean + */ + public function isOutputDisabled() + { + return $this->outputDisabled; + } + /** * Returns the current output of the process (STDOUT). * * @return string The process output * + * @throws LogicException in case the output has been disabled. + * * @api */ public function getOutput() { + if ($this->outputDisabled) { + throw new LogicException('Output has been disabled.'); + } + $this->readPipes(false, defined('PHP_WINDOWS_VERSION_BUILD') ? !$this->processInformation['running'] : true); return $this->stdout; @@ -420,6 +478,8 @@ public function getOutput() * In comparison with the getOutput method which always return the whole * output, this one returns the new output since the last call. * + * @throws LogicException in case the output has been disabled. + * * @return string The process output since the last call */ public function getIncrementalOutput() @@ -450,10 +510,16 @@ public function clearOutput() * * @return string The process error output * + * @throws LogicException in case the output has been disabled. + * * @api */ public function getErrorOutput() { + if ($this->outputDisabled) { + throw new LogicException('Output has been disabled.'); + } + $this->readPipes(false, defined('PHP_WINDOWS_VERSION_BUILD') ? !$this->processInformation['running'] : true); return $this->stderr; @@ -466,6 +532,8 @@ public function getErrorOutput() * whole error output, this one returns the new error output since the last * call. * + * @throws LogicException in case the output has been disabled. + * * @return string The process error output since the last call */ public function getIncrementalErrorOutput() @@ -1083,7 +1151,7 @@ public static function isPtySupported() private function getDescriptors() { $this->processPipes = new ProcessPipes($this->useFileHandles, $this->tty, $this->pty); - $descriptors = $this->processPipes->getDescriptors(); + $descriptors = $this->processPipes->getDescriptors($this->outputDisabled); if (!$this->useFileHandles && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) { // last exit code is output on the fourth pipe and caught to work around --enable-sigchild diff --git a/src/Symfony/Component/Process/ProcessBuilder.php b/src/Symfony/Component/Process/ProcessBuilder.php index b6168feb62d0..26a0a9b32d51 100644 --- a/src/Symfony/Component/Process/ProcessBuilder.php +++ b/src/Symfony/Component/Process/ProcessBuilder.php @@ -29,6 +29,7 @@ class ProcessBuilder private $options = array(); private $inheritEnv = true; private $prefix = array(); + private $outputDisabled = false; public function __construct(array $arguments = array()) { @@ -154,6 +155,30 @@ public function setOption($name, $value) return $this; } + /** + * Disables fetching output and error output from the underlying process. + * + * @return Process + */ + public function disableOutput() + { + $this->outputDisabled = true; + + return $this; + } + + /** + * Enables fetching output and error output from the underlying process. + * + * @return Process + */ + public function enableOutput() + { + $this->outputDisabled = false; + + return $this; + } + public function getProcess() { if (0 === count($this->prefix) && 0 === count($this->arguments)) { @@ -172,6 +197,12 @@ public function getProcess() $env = $this->env; } - return new Process($script, $this->cwd, $env, $this->stdin, $this->timeout, $options); + $process = new Process($script, $this->cwd, $env, $this->stdin, $this->timeout, $options); + + if ($this->outputDisabled) { + $process->disableOutput(); + } + + return $process; } } diff --git a/src/Symfony/Component/Process/ProcessPipes.php b/src/Symfony/Component/Process/ProcessPipes.php index fa107d8c0341..b3040314c1df 100644 --- a/src/Symfony/Component/Process/ProcessPipes.php +++ b/src/Symfony/Component/Process/ProcessPipes.php @@ -101,10 +101,22 @@ public function closeUnixPipes() /** * Returns an array of descriptors for the use of proc_open. * + * @param Boolean $disableOutput Whether to redirect STDOUT and STDERR to /dev/null or not. + * * @return array */ - public function getDescriptors() + public function getDescriptors($disableOutput) { + if ($disableOutput) { + $nullstream = fopen(defined('PHP_WINDOWS_VERSION_BUILD') ? 'NUL' : '/dev/null', 'c'); + + return array( + array('pipe', 'r'), + $nullstream, + $nullstream, + ); + } + if ($this->useFiles) { return array( array('pipe', 'r'), diff --git a/src/Symfony/Component/Process/Tests/AbstractProcessTest.php b/src/Symfony/Component/Process/Tests/AbstractProcessTest.php index f5291d0e15bb..75981c97d346 100644 --- a/src/Symfony/Component/Process/Tests/AbstractProcessTest.php +++ b/src/Symfony/Component/Process/Tests/AbstractProcessTest.php @@ -700,6 +700,84 @@ public function testSignalWithWrongNonIntSignal() $process->signal('Céphalopodes'); } + public function testDisableOutputDisablesTheOutput() + { + $p = $this->getProcess('php -r "usleep(500000);"'); + $this->assertFalse($p->isOutputDisabled()); + $p->disableOutput(); + $this->assertTrue($p->isOutputDisabled()); + $p->enableOutput(); + $this->assertFalse($p->isOutputDisabled()); + } + + public function testDisableOutputWhileRunningThrowsException() + { + $p = $this->getProcess('php -r "usleep(500000);"'); + $p->start(); + $this->setExpectedException('Symfony\Component\Process\Exception\RuntimeException', 'Disabling output while the process is running is not possible.'); + $p->disableOutput(); + } + + public function testEnableOutputWhileRunningThrowsException() + { + $p = $this->getProcess('php -r "usleep(500000);"'); + $p->disableOutput(); + $p->start(); + $this->setExpectedException('Symfony\Component\Process\Exception\RuntimeException', 'Enabling output while the process is running is not possible.'); + $p->enableOutput(); + } + + public function testEnableOrDisableOutputAfterRunDoesNotThrowException() + { + $p = $this->getProcess('php -r "usleep(500000);"'); + $p->disableOutput(); + $p->start(); + $p->wait(); + $p->enableOutput(); + $p->disableOutput(); + } + + /** + * @dataProvider provideStartMethods + */ + public function testStartWithACallbackAndDisabledOutput($startMethod) + { + $p = $this->getProcess('php -r "usleep(500000);"'); + $p->disableOutput(); + $this->setExpectedException('Symfony\Component\Process\Exception\LogicException', 'Output has been disabled, enable it to allow the use of a callback.'); + call_user_func(array($p, $startMethod), function () {}); + } + + public function provideStartMethods() + { + return array( + array('start'), + array('run'), + array('mustRun'), + ); + } + + /** + * @dataProvider provideOutputFetchingMethods + */ + public function testGetOutputWhileDisabled($fetchMethod) + { + $p = $this->getProcess('php -r "usleep(500000);"'); + $p->disableOutput(); + $this->setExpectedException('Symfony\Component\Process\Exception\LogicException', 'Output has been disabled.'); + call_user_func(array($p, $fetchMethod)); + } + + public function provideOutputFetchingMethods() + { + return array( + array('getOutput'), + array('getIncrementalOutput'), + array('getErrorOutput'), + array('getIncrementalErrorOutput'), + ); + } + public function responsesCodeProvider() { return array( diff --git a/src/Symfony/Component/Process/Tests/ProcessBuilderTest.php b/src/Symfony/Component/Process/Tests/ProcessBuilderTest.php index 2982aff9f988..6262fc3cd7f5 100644 --- a/src/Symfony/Component/Process/Tests/ProcessBuilderTest.php +++ b/src/Symfony/Component/Process/Tests/ProcessBuilderTest.php @@ -193,4 +193,23 @@ public function testShouldNotThrowALogicExceptionIfNoPrefix() $this->assertEquals("'/usr/bin/php'", $process->getCommandLine()); } } + + public function testShouldReturnProcessWithDisabledOutput() + { + $process = ProcessBuilder::create(array('/usr/bin/php')) + ->disableOutput() + ->getProcess(); + + $this->assertTrue($process->isOutputDisabled()); + } + + public function testShouldReturnProcessWithEnabledOutput() + { + $process = ProcessBuilder::create(array('/usr/bin/php')) + ->disableOutput() + ->enableOutput() + ->getProcess(); + + $this->assertFalse($process->isOutputDisabled()); + } }