diff --git a/src/Symfony/Component/Process/Pipes/AbstractPipes.php b/src/Symfony/Component/Process/Pipes/AbstractPipes.php index f2fd35eb43c20..116ee3898e073 100644 --- a/src/Symfony/Component/Process/Pipes/AbstractPipes.php +++ b/src/Symfony/Component/Process/Pipes/AbstractPipes.php @@ -22,16 +22,15 @@ abstract class AbstractPipes implements PipesInterface public $pipes = array(); /** @var string */ - protected $inputBuffer = ''; - /** @var resource|null */ - protected $input; - + private $inputBuffer = ''; + /** @var resource|\Iterator|null */ + private $input; /** @var bool */ private $blocked = true; public function __construct($input) { - if (is_resource($input)) { + if (is_resource($input) || $input instanceof \Iterator) { $this->input = $input; } elseif (is_string($input)) { $this->inputBuffer = $input; @@ -76,7 +75,7 @@ protected function unblock() foreach ($this->pipes as $pipe) { stream_set_blocking($pipe, 0); } - if (null !== $this->input) { + if (is_resource($this->input)) { stream_set_blocking($this->input, 0); } @@ -91,9 +90,21 @@ protected function write() if (!isset($this->pipes[0])) { return; } + $input = $this->input; + + if ($input instanceof \Iterator) { + if (!$input->valid()) { + $input = null; + } elseif (is_resource($input = $input->current())) { + stream_set_blocking($input, 0); + } else { + $this->inputBuffer .= $input; + $this->input->next(); + $input = null; + } + } - $e = array(); - $r = null !== $this->input ? array($this->input) : $e; + $r = $e = array(); $w = array($this->pipes[0]); // let's have a look if something changed in streams @@ -109,8 +120,7 @@ protected function write() return array($this->pipes[0]); } } - - foreach ($r as $input) { + if ($input) { for (;;) { $data = fread($input, self::CHUNK_SIZE); if (!isset($data[0])) { @@ -124,16 +134,19 @@ protected function write() return array($this->pipes[0]); } } - if (!isset($data[0]) && feof($input)) { - // no more data to read on input resource - // use an empty buffer in the next reads - $this->input = null; + if (feof($input)) { + if ($this->input instanceof \Iterator) { + $this->input->next(); + } else { + $this->input = null; + } } } } // no input to read on resource, buffer is empty - if (null === $this->input && !isset($this->inputBuffer[0])) { + if (!isset($this->inputBuffer[0]) && !($this->input instanceof \Iterator ? $this->input->valid() : $this->input)) { + $this->input = null; fclose($this->pipes[0]); unset($this->pipes[0]); } diff --git a/src/Symfony/Component/Process/Process.php b/src/Symfony/Component/Process/Process.php index cf3cf68f96388..0be78c0fb1b95 100644 --- a/src/Symfony/Component/Process/Process.php +++ b/src/Symfony/Component/Process/Process.php @@ -1200,6 +1200,9 @@ public function hasCallback() */ private function getDescriptors() { + if ($this->input instanceof \Iterator) { + $this->input->rewind(); + } if ('\\' === DIRECTORY_SEPARATOR) { $this->processPipes = WindowsPipes::create($this, $this->input); } else { diff --git a/src/Symfony/Component/Process/ProcessUtils.php b/src/Symfony/Component/Process/ProcessUtils.php index 6cbfa22ce6b95..3825df2d25688 100644 --- a/src/Symfony/Component/Process/ProcessUtils.php +++ b/src/Symfony/Component/Process/ProcessUtils.php @@ -93,8 +93,14 @@ public static function validateInput($caller, $input) if (is_scalar($input)) { return (string) $input; } + if ($input instanceof \Iterator) { + return $input; + } + if ($input instanceof \Traversable) { + return new \IteratorIterator($input); + } - throw new InvalidArgumentException(sprintf('%s only accepts strings or stream resources.', $caller)); + throw new InvalidArgumentException(sprintf('%s only accepts strings, Traversable objects or stream resources.', $caller)); } return $input; diff --git a/src/Symfony/Component/Process/Tests/ProcessBuilderTest.php b/src/Symfony/Component/Process/Tests/ProcessBuilderTest.php index 1b5056d1bb104..3e29af710f999 100644 --- a/src/Symfony/Component/Process/Tests/ProcessBuilderTest.php +++ b/src/Symfony/Component/Process/Tests/ProcessBuilderTest.php @@ -215,7 +215,7 @@ public function testShouldReturnProcessWithEnabledOutput() /** * @expectedException \Symfony\Component\Process\Exception\InvalidArgumentException - * @expectedExceptionMessage Symfony\Component\Process\ProcessBuilder::setInput only accepts strings or stream resources. + * @expectedExceptionMessage Symfony\Component\Process\ProcessBuilder::setInput only accepts strings, Traversable objects or stream resources. */ public function testInvalidInput() { diff --git a/src/Symfony/Component/Process/Tests/ProcessTest.php b/src/Symfony/Component/Process/Tests/ProcessTest.php index 6f87c077c7d47..ec447631100b7 100644 --- a/src/Symfony/Component/Process/Tests/ProcessTest.php +++ b/src/Symfony/Component/Process/Tests/ProcessTest.php @@ -231,7 +231,7 @@ public function testSetInputWhileRunningThrowsAnException() /** * @dataProvider provideInvalidInputValues * @expectedException \Symfony\Component\Process\Exception\InvalidArgumentException - * @expectedExceptionMessage Symfony\Component\Process\Process::setInput only accepts strings or stream resources. + * @expectedExceptionMessage Symfony\Component\Process\Process::setInput only accepts strings, Traversable objects or stream resources. */ public function testInvalidInput($value) { @@ -1156,6 +1156,35 @@ public function provideVariousIncrementals() { ); } + public function testIteratorInput() + { + $nextData = 'ping'; + $input = function () use (&$nextData) { + while (false !== $nextData) { + yield $nextData; + yield $nextData = ''; + } + }; + $input = $input(); + + $process = new Process(self::$phpBin.' -r '.escapeshellarg('stream_copy_to_stream(STDIN, STDOUT);')); + $process->setInput($input); + $process->start(function ($type, $data) use ($input, &$nextData) { + if ('ping' === $data) { + $h = fopen('php://memory', 'r+'); + fwrite($h, 'pong'); + rewind($h); + $nextData = $h; + $input->next(); + } else { + $nextData = false; + } + }); + + $process->wait(); + $this->assertSame('pingpong', $process->getOutput()); + } + /** * @param string $commandline * @param null|string $cwd pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy