[Process] Add feature \"wait until callback\" to process class

This commit is contained in:
Maxime Veber 2018-06-27 14:55:52 +02:00 committed by Fabien Potencier
parent 620094a12c
commit 27eaf83b63
4 changed files with 97 additions and 3 deletions

View File

@ -7,6 +7,8 @@ CHANGELOG
* added the `Process::fromShellCommandline()` to run commands in a shell wrapper
* deprecated passing a command as string when creating a `Process` instance
* deprecated the `Process::setCommandline()` and the `PhpProcess::setPhpBinary()` methods
* added the `Process::waitUntil()` method to wait for the process only for a
specific output, then continue the normal execution of your application
4.1.0
-----

View File

@ -407,7 +407,7 @@ class Process implements \IteratorAggregate
if (null !== $callback) {
if (!$this->processPipes->haveReadSupport()) {
$this->stop(0);
throw new \LogicException('Pass the callback to the Process::start method or enableOutput to use a callback with Process::wait');
throw new \LogicException('Pass the callback to the "Process::start" method or call enableOutput to use a callback with "Process::wait"');
}
$this->callback = $this->buildCallback($callback);
}
@ -429,6 +429,45 @@ class Process implements \IteratorAggregate
return $this->exitcode;
}
/**
* Waits until the callback returns true.
*
* The callback receives the type of output (out or err) and some bytes
* from the output in real-time while writing the standard input to the process.
* It allows to have feedback from the independent process during execution.
*
* @param callable $callback
*
* @throws RuntimeException When process timed out
* @throws LogicException When process is not yet started
*/
public function waitUntil(callable $callback)
{
$this->requireProcessIsStarted(__FUNCTION__);
$this->updateStatus(false);
if (!$this->processPipes->haveReadSupport()) {
$this->stop(0);
throw new \LogicException('Pass the callback to the "Process::start" method or call enableOutput to use a callback with "Process::waitUntil".');
}
$callback = $this->buildCallback($callback);
$wait = true;
do {
$this->checkTimeout();
$running = '\\' === \DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen();
$output = $this->processPipes->readAndWrite($running, '\\' !== \DIRECTORY_SEPARATOR || !$running);
foreach ($output as $type => $data) {
if (3 !== $type) {
$wait = !$callback(self::STDOUT === $type ? self::OUT : self::ERR, $data);
} elseif (!isset($this->fallbackStatus['signaled'])) {
$this->fallbackStatus['exitcode'] = (int) $data;
}
}
} while ($wait);
}
/**
* Returns the Pid (process identifier), if applicable.
*
@ -1264,7 +1303,7 @@ class Process implements \IteratorAggregate
if ($this->outputDisabled) {
return function ($type, $data) use ($callback) {
if (null !== $callback) {
\call_user_func($callback, $type, $data);
return \call_user_func($callback, $type, $data);
}
};
}
@ -1279,7 +1318,7 @@ class Process implements \IteratorAggregate
}
if (null !== $callback) {
\call_user_func($callback, $type, $data);
return \call_user_func($callback, $type, $data);
}
};
}

View File

@ -0,0 +1,26 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
$outputs = array(
'First iteration output',
'Second iteration output',
'One more iteration output',
'This took more time',
'This one was sooooo slow',
);
$iterationTime = 10000;
foreach ($outputs as $output) {
usleep($iterationTime);
$iterationTime *= 10;
echo $output."\n";
}

View File

@ -133,6 +133,33 @@ class ProcessTest extends TestCase
$this->assertLessThan(15, microtime(true) - $start);
}
public function testWaitUntilSpecificOutput()
{
$p = $this->getProcess(array(self::$phpBin, __DIR__.'/KillableProcessWithOutput.php'));
$p->start();
$start = microtime(true);
$completeOutput = '';
$p->waitUntil(function ($type, $output) use (&$completeOutput) {
$completeOutput .= $output;
if (false !== strpos($output, 'One more')) {
return true;
}
return false;
});
$p->stop();
if ('\\' === \DIRECTORY_SEPARATOR) {
// Windows is slower
$this->assertLessThan(15, microtime(true) - $start);
} else {
$this->assertLessThan(2, microtime(true) - $start);
}
$this->assertEquals("First iteration output\nSecond iteration output\nOne more iteration output\n", $completeOutput);
}
public function testAllOutputIsActuallyReadOnTermination()
{
// this code will result in a maximum of 2 reads of 8192 bytes by calling