|
27 | 27 | * @author Fabien Potencier <[email protected]>
|
28 | 28 | * @author Romain Neutron <[email protected]>
|
29 | 29 | */
|
30 |
| -class Process |
| 30 | +class Process implements \IteratorAggregate |
31 | 31 | {
|
32 | 32 | const ERR = 'err';
|
33 | 33 | const OUT = 'out';
|
@@ -362,7 +362,7 @@ public function wait(callable $callback = null)
|
362 | 362 | do {
|
363 | 363 | $this->checkTimeout();
|
364 | 364 | $running = '\\' === DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen();
|
365 |
| - $this->readPipes($running, '\\' !== DIRECTORY_SEPARATOR || !$running); |
| 365 | + $this->readPipes($running, '\\' !== DIRECTORY_SEPARATOR || !$running, $this->callback); |
366 | 366 | } while ($running);
|
367 | 367 |
|
368 | 368 | while ($this->isRunning()) {
|
@@ -498,6 +498,54 @@ public function getIncrementalOutput()
|
498 | 498 | return $latest;
|
499 | 499 | }
|
500 | 500 |
|
| 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 | + |
501 | 549 | /**
|
502 | 550 | * Clears the process output.
|
503 | 551 | *
|
@@ -1261,7 +1309,7 @@ protected function updateStatus($blocking)
|
1261 | 1309 | $this->processInformation = proc_get_status($this->process);
|
1262 | 1310 | $running = $this->processInformation['running'];
|
1263 | 1311 |
|
1264 |
| - $this->readPipes($running && $blocking, '\\' !== DIRECTORY_SEPARATOR || !$running); |
| 1312 | + $this->readPipes($running && $blocking, '\\' !== DIRECTORY_SEPARATOR || !$running, $this->callback); |
1265 | 1313 |
|
1266 | 1314 | if ($this->fallbackStatus && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) {
|
1267 | 1315 | $this->processInformation = $this->fallbackStatus + $this->processInformation;
|
@@ -1296,19 +1344,20 @@ protected function isSigchildEnabled()
|
1296 | 1344 | /**
|
1297 | 1345 | * Reads pipes for the freshest output.
|
1298 | 1346 | *
|
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. |
1300 | 1349 | *
|
1301 | 1350 | * @throws LogicException in case output has been disabled or process is not started
|
1302 | 1351 | */
|
1303 |
| - private function readPipesForOutput($caller) |
| 1352 | + private function readPipesForOutput($caller, $blocking = false) |
1304 | 1353 | {
|
1305 | 1354 | if ($this->outputDisabled) {
|
1306 | 1355 | throw new LogicException('Output has been disabled.');
|
1307 | 1356 | }
|
1308 | 1357 |
|
1309 | 1358 | $this->requireProcessIsStarted($caller);
|
1310 | 1359 |
|
1311 |
| - $this->updateStatus(false); |
| 1360 | + $this->updateStatus($blocking); |
1312 | 1361 | }
|
1313 | 1362 |
|
1314 | 1363 | /**
|
@@ -1336,14 +1385,14 @@ private function validateTimeout($timeout)
|
1336 | 1385 | /**
|
1337 | 1386 | * Reads pipes, executes callback.
|
1338 | 1387 | *
|
1339 |
| - * @param bool $blocking Whether to use blocking calls or not. |
1340 |
| - * @param bool $close Whether to close file handles or not. |
| 1388 | + * @param bool $blocking Whether to use blocking calls or not. |
| 1389 | + * @param bool $close Whether to close file handles or not. |
| 1390 | + * @param \Closure $callback The internal callback used to handle the data. |
1341 | 1391 | */
|
1342 |
| - private function readPipes($blocking, $close) |
| 1392 | + private function readPipes($blocking, $close, $callback) |
1343 | 1393 | {
|
1344 | 1394 | $result = $this->processPipes->readAndWrite($blocking, $close);
|
1345 | 1395 |
|
1346 |
| - $callback = $this->callback; |
1347 | 1396 | foreach ($result as $type => $data) {
|
1348 | 1397 | if (3 !== $type) {
|
1349 | 1398 | $callback($type === self::STDOUT ? self::OUT : self::ERR, $data);
|
|
0 commit comments