[Process] Add feature \"wait until callback\" to process class
This commit is contained in:
parent
620094a12c
commit
27eaf83b63
@ -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
|
||||
-----
|
||||
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -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";
|
||||
}
|
@ -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
|
||||
|
Reference in New Issue
Block a user