Skip to content

Commit ac00c28

Browse files
committed
Add feature "wait until callback" to process class
A feature for waiting the process to complete is already present, but in some cases you may want to just wait for the process to be ready. This kind of process generaly output things when they are ready. With this feature you will be able to (for example) wait until the process is ready.
1 parent 0252a00 commit ac00c28

File tree

3 files changed

+89
-2
lines changed

3 files changed

+89
-2
lines changed

src/Symfony/Component/Process/Process.php

+41-2
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,45 @@ public function wait(callable $callback = null)
396396
return $this->exitcode;
397397
}
398398

399+
/**
400+
* Waits until the callback return true.
401+
*
402+
* The callback receives the type of output (out or err) and some bytes
403+
* from the output in real-time while writing the standard input to the process.
404+
* It allows to have feedback from the independent process during execution.
405+
*
406+
* @param callable $callback
407+
*
408+
* @throws RuntimeException When process timed out
409+
* @throws LogicException When process is not yet started
410+
*/
411+
public function waitUntil(callable $callback)
412+
{
413+
$this->requireProcessIsStarted(__FUNCTION__);
414+
$this->updateStatus(false);
415+
416+
if (!$this->processPipes->haveReadSupport()) {
417+
$this->stop(0);
418+
throw new \LogicException('Pass the callback to the Process::start method or enableOutput to use a callback with Process::wait');
419+
}
420+
$callback = $this->buildCallback($callback);
421+
422+
$wait = true;
423+
do {
424+
$this->checkTimeout();
425+
$running = '\\' === DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen();
426+
$output = $this->processPipes->readAndWrite($running, '\\' !== DIRECTORY_SEPARATOR || !$running);
427+
428+
foreach ($output as $type => $data) {
429+
if (3 !== $type) {
430+
$wait = !$callback(self::STDOUT === $type ? self::OUT : self::ERR, $data);
431+
} elseif (!isset($this->fallbackStatus['signaled'])) {
432+
$this->fallbackStatus['exitcode'] = (int) $data;
433+
}
434+
}
435+
} while ($wait);
436+
}
437+
399438
/**
400439
* Returns the Pid (process identifier), if applicable.
401440
*
@@ -1227,7 +1266,7 @@ protected function buildCallback(callable $callback = null)
12271266
if ($this->outputDisabled) {
12281267
return function ($type, $data) use ($callback) {
12291268
if (null !== $callback) {
1230-
call_user_func($callback, $type, $data);
1269+
return $callback($type, $data);
12311270
}
12321271
};
12331272
}
@@ -1242,7 +1281,7 @@ protected function buildCallback(callable $callback = null)
12421281
}
12431282

12441283
if (null !== $callback) {
1245-
call_user_func($callback, $type, $data);
1284+
return $callback($type, $data);
12461285
}
12471286
};
12481287
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
$outputs = [
13+
'First iteration output',
14+
'Second iteration output',
15+
'One more iteration output',
16+
'This took more time',
17+
'This one was sooooo slow',
18+
];
19+
20+
$iterationTime = 10000;
21+
22+
foreach ($outputs as $output) {
23+
usleep($iterationTime);
24+
$iterationTime *= 10;
25+
echo $output . "\n";
26+
}

src/Symfony/Component/Process/Tests/ProcessTest.php

+22
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,28 @@ public function testStopWithTimeoutIsActuallyWorking()
133133
$this->assertLessThan(15, microtime(true) - $start);
134134
}
135135

136+
public function testWaitUntilSpecificOutput()
137+
{
138+
$p = $this->getProcess(array(self::$phpBin, __DIR__.'/KillableProcessWithOutput.php'));
139+
$p->start();
140+
141+
$start = microtime(true);
142+
143+
$completeOutput = '';
144+
$p->waitUntil(function ($type, $output) use (&$completeOutput) {
145+
$completeOutput .= $output;
146+
if (strpos($output, 'One more') !== false) {
147+
return true;
148+
}
149+
150+
return false;
151+
});
152+
$p->stop();
153+
154+
$this->assertEquals("First iteration output\nSecond iteration output\nOne more iteration output\n", $completeOutput);
155+
$this->assertLessThan(15, microtime(true) - $start);
156+
}
157+
136158
public function testAllOutputIsActuallyReadOnTermination()
137159
{
138160
// this code will result in a maximum of 2 reads of 8192 bytes by calling

0 commit comments

Comments
 (0)