feature #10425 [Process] Add Process::disableOutput and Process::enableOutput methods (romainneutron)

This PR was merged into the 2.5-dev branch.

Discussion
----------

[Process] Add Process::disableOutput and Process::enableOutput methods

| Q             | A
| ------------- | ---
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #9007
| License       | MIT
| Doc PR        | https://github.com/symfony/symfony-docs/pull/3664

This is another implementation for #9007 that allows to disable the output/error-output storage in a Process in order to avoid using memory.
This is particularly useful when the process outputs large data and it's not read.

Commits
-------

a891e14 [Process] Add Process::disableOutput and Process::enableOutput methods
This commit is contained in:
Fabien Potencier 2014-03-12 19:33:59 +01:00
commit 5e0bb71b90
5 changed files with 211 additions and 3 deletions

View File

@ -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 @@ class Process
* @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 @@ class Process
*
* @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 @@ class Process
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 @@ class Process
* 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 @@ class Process
*
* @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 @@ class Process
* 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 @@ class Process
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

View File

@ -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 @@ class ProcessBuilder
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 @@ class ProcessBuilder
$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;
}
}

View File

@ -101,10 +101,22 @@ class ProcessPipes
/**
* 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'),

View File

@ -700,6 +700,84 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
$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(

View File

@ -193,4 +193,23 @@ class ProcessBuilderTest extends \PHPUnit_Framework_TestCase
$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());
}
}