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:
commit
5e0bb71b90
@ -54,6 +54,7 @@ class Process
|
|||||||
private $exitcode;
|
private $exitcode;
|
||||||
private $fallbackExitcode;
|
private $fallbackExitcode;
|
||||||
private $processInformation;
|
private $processInformation;
|
||||||
|
private $outputDisabled = false;
|
||||||
private $stdout;
|
private $stdout;
|
||||||
private $stderr;
|
private $stderr;
|
||||||
private $enhanceWindowsCompatibility;
|
private $enhanceWindowsCompatibility;
|
||||||
@ -193,6 +194,7 @@ class Process
|
|||||||
* @return integer The exit status code
|
* @return integer The exit status code
|
||||||
*
|
*
|
||||||
* @throws RuntimeException When process can't be launch or is stopped
|
* @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
|
* @api
|
||||||
*/
|
*/
|
||||||
@ -244,12 +246,16 @@ class Process
|
|||||||
*
|
*
|
||||||
* @throws RuntimeException When process can't be launch or is stopped
|
* @throws RuntimeException When process can't be launch or is stopped
|
||||||
* @throws RuntimeException When process is already running
|
* @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)
|
public function start($callback = null)
|
||||||
{
|
{
|
||||||
if ($this->isRunning()) {
|
if ($this->isRunning()) {
|
||||||
throw new RuntimeException('Process is already running');
|
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->resetProcessData();
|
||||||
$this->starttime = $this->lastOutputTime = microtime(true);
|
$this->starttime = $this->lastOutputTime = microtime(true);
|
||||||
@ -400,15 +406,67 @@ class Process
|
|||||||
return $this;
|
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).
|
* Returns the current output of the process (STDOUT).
|
||||||
*
|
*
|
||||||
* @return string The process output
|
* @return string The process output
|
||||||
*
|
*
|
||||||
|
* @throws LogicException in case the output has been disabled.
|
||||||
|
*
|
||||||
* @api
|
* @api
|
||||||
*/
|
*/
|
||||||
public function getOutput()
|
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);
|
$this->readPipes(false, defined('PHP_WINDOWS_VERSION_BUILD') ? !$this->processInformation['running'] : true);
|
||||||
|
|
||||||
return $this->stdout;
|
return $this->stdout;
|
||||||
@ -420,6 +478,8 @@ class Process
|
|||||||
* In comparison with the getOutput method which always return the whole
|
* In comparison with the getOutput method which always return the whole
|
||||||
* output, this one returns the new output since the last call.
|
* 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
|
* @return string The process output since the last call
|
||||||
*/
|
*/
|
||||||
public function getIncrementalOutput()
|
public function getIncrementalOutput()
|
||||||
@ -450,10 +510,16 @@ class Process
|
|||||||
*
|
*
|
||||||
* @return string The process error output
|
* @return string The process error output
|
||||||
*
|
*
|
||||||
|
* @throws LogicException in case the output has been disabled.
|
||||||
|
*
|
||||||
* @api
|
* @api
|
||||||
*/
|
*/
|
||||||
public function getErrorOutput()
|
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);
|
$this->readPipes(false, defined('PHP_WINDOWS_VERSION_BUILD') ? !$this->processInformation['running'] : true);
|
||||||
|
|
||||||
return $this->stderr;
|
return $this->stderr;
|
||||||
@ -466,6 +532,8 @@ class Process
|
|||||||
* whole error output, this one returns the new error output since the last
|
* whole error output, this one returns the new error output since the last
|
||||||
* call.
|
* call.
|
||||||
*
|
*
|
||||||
|
* @throws LogicException in case the output has been disabled.
|
||||||
|
*
|
||||||
* @return string The process error output since the last call
|
* @return string The process error output since the last call
|
||||||
*/
|
*/
|
||||||
public function getIncrementalErrorOutput()
|
public function getIncrementalErrorOutput()
|
||||||
@ -1083,7 +1151,7 @@ class Process
|
|||||||
private function getDescriptors()
|
private function getDescriptors()
|
||||||
{
|
{
|
||||||
$this->processPipes = new ProcessPipes($this->useFileHandles, $this->tty, $this->pty);
|
$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()) {
|
if (!$this->useFileHandles && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
|
||||||
// last exit code is output on the fourth pipe and caught to work around --enable-sigchild
|
// last exit code is output on the fourth pipe and caught to work around --enable-sigchild
|
||||||
|
@ -29,6 +29,7 @@ class ProcessBuilder
|
|||||||
private $options = array();
|
private $options = array();
|
||||||
private $inheritEnv = true;
|
private $inheritEnv = true;
|
||||||
private $prefix = array();
|
private $prefix = array();
|
||||||
|
private $outputDisabled = false;
|
||||||
|
|
||||||
public function __construct(array $arguments = array())
|
public function __construct(array $arguments = array())
|
||||||
{
|
{
|
||||||
@ -154,6 +155,30 @@ class ProcessBuilder
|
|||||||
return $this;
|
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()
|
public function getProcess()
|
||||||
{
|
{
|
||||||
if (0 === count($this->prefix) && 0 === count($this->arguments)) {
|
if (0 === count($this->prefix) && 0 === count($this->arguments)) {
|
||||||
@ -172,6 +197,12 @@ class ProcessBuilder
|
|||||||
$env = $this->env;
|
$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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -101,10 +101,22 @@ class ProcessPipes
|
|||||||
/**
|
/**
|
||||||
* Returns an array of descriptors for the use of proc_open.
|
* 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
|
* @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) {
|
if ($this->useFiles) {
|
||||||
return array(
|
return array(
|
||||||
array('pipe', 'r'),
|
array('pipe', 'r'),
|
||||||
|
@ -700,6 +700,84 @@ abstract class AbstractProcessTest extends \PHPUnit_Framework_TestCase
|
|||||||
$process->signal('Céphalopodes');
|
$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()
|
public function responsesCodeProvider()
|
||||||
{
|
{
|
||||||
return array(
|
return array(
|
||||||
|
@ -193,4 +193,23 @@ class ProcessBuilderTest extends \PHPUnit_Framework_TestCase
|
|||||||
$this->assertEquals("'/usr/bin/php'", $process->getCommandLine());
|
$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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user