Skip to content

Commit 22e0eae

Browse files
committed
feature #18414 [Process] Implement IteratorAggregate to stream output (nicolas-grekas)
This PR was merged into the 3.1-dev branch. Discussion ---------- [Process] Implement IteratorAggregate to stream output | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | - | License | MIT | Doc PR | symfony/symfony-docs#6424 Sibling to #18386 for iterating of the output streams. Commits ------- 5947f5d [Process] Implement IteratorAggregate to stream output
2 parents 804b924 + 5947f5d commit 22e0eae

File tree

2 files changed

+89
-4
lines changed

2 files changed

+89
-4
lines changed

src/Symfony/Component/Process/Process.php

+53-4
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
* @author Fabien Potencier <fabien@symfony.com>
2828
* @author Romain Neutron <imprec@gmail.com>
2929
*/
30-
class Process
30+
class Process implements \IteratorAggregate
3131
{
3232
const ERR = 'err';
3333
const OUT = 'out';
@@ -498,6 +498,54 @@ public function getIncrementalOutput()
498498
return $latest;
499499
}
500500

501+
/**
502+
* Returns an iterator to the output of the process, with the output type as keys (Process::OUT/ERR).
503+
*
504+
* @param bool $blocking Whether to use a blocking read call.
505+
* @param bool $clearOutput Whether to clear or keep output in memory.
506+
*
507+
* @throws LogicException in case the output has been disabled
508+
* @throws LogicException In case the process is not started
509+
*
510+
* @return \Generator
511+
*/
512+
public function getIterator($blocking = true, $clearOutput = true)
513+
{
514+
$this->readPipesForOutput(__FUNCTION__, false);
515+
516+
while (null !== $this->callback) {
517+
$out = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset);
518+
519+
if (isset($out[0])) {
520+
if ($clearOutput) {
521+
$this->clearOutput();
522+
} else {
523+
$this->incrementalOutputOffset = ftell($this->stdout);
524+
}
525+
526+
yield self::OUT => $out;
527+
}
528+
529+
$err = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset);
530+
531+
if (isset($err[0])) {
532+
if ($clearOutput) {
533+
$this->clearErrorOutput();
534+
} else {
535+
$this->incrementalErrorOutputOffset = ftell($this->stderr);
536+
}
537+
538+
yield self::ERR => $err;
539+
}
540+
541+
if (!$blocking && !isset($out[0]) && !isset($err[0])) {
542+
yield self::OUT => '';
543+
}
544+
545+
$this->readPipesForOutput(__FUNCTION__, $blocking);
546+
}
547+
}
548+
501549
/**
502550
* Clears the process output.
503551
*
@@ -1296,19 +1344,20 @@ protected function isSigchildEnabled()
12961344
/**
12971345
* Reads pipes for the freshest output.
12981346
*
1299-
* @param $caller The name of the method that needs fresh outputs
1347+
* @param string $caller The name of the method that needs fresh outputs
1348+
* @param bool $blocking Whether to use blocking calls or not.
13001349
*
13011350
* @throws LogicException in case output has been disabled or process is not started
13021351
*/
1303-
private function readPipesForOutput($caller)
1352+
private function readPipesForOutput($caller, $blocking = false)
13041353
{
13051354
if ($this->outputDisabled) {
13061355
throw new LogicException('Output has been disabled.');
13071356
}
13081357

13091358
$this->requireProcessIsStarted($caller);
13101359

1311-
$this->updateStatus(false);
1360+
$this->updateStatus($blocking);
13121361
}
13131362

13141363
/**

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

+36
Original file line numberDiff line numberDiff line change
@@ -1270,6 +1270,42 @@ public function testInputStreamOnEmpty()
12701270
$this->assertSame('123456', $process->getOutput());
12711271
}
12721272

1273+
public function testIteratorOutput()
1274+
{
1275+
$input = new InputStream();
1276+
1277+
$process = new Process(self::$phpBin.' -r '.escapeshellarg('fwrite(STDOUT, 123); fwrite(STDERR, 234); fwrite(STDOUT, fread(STDIN, 3)); fwrite(STDERR, 456);'));
1278+
$process->setInput($input);
1279+
$process->start();
1280+
$output = array();
1281+
1282+
foreach ($process as $type => $data) {
1283+
$output[] = array($type, $data);
1284+
break;
1285+
}
1286+
$expectedOutput = array(
1287+
array($process::OUT, '123'),
1288+
);
1289+
$this->assertSame($expectedOutput, $output);
1290+
1291+
$input->write(345);
1292+
1293+
foreach ($process as $type => $data) {
1294+
$output[] = array($type, $data);
1295+
}
1296+
1297+
$this->assertSame('', $process->getOutput());
1298+
$this->assertFalse($process->isRunning());
1299+
1300+
$expectedOutput = array(
1301+
array($process::OUT, '123'),
1302+
array($process::ERR, '234'),
1303+
array($process::OUT, '345'),
1304+
array($process::ERR, '456'),
1305+
);
1306+
$this->assertSame($expectedOutput, $output);
1307+
}
1308+
12731309
/**
12741310
* @param string $commandline
12751311
* @param null|string $cwd

0 commit comments

Comments
 (0)