Skip to content

Commit c61077c

Browse files
committed
feature #18350 [Process] Accept Traversable input (nicolas-grekas)
This PR was merged into the 3.1-dev branch. Discussion ---------- [Process] Accept Traversable input | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | no | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | #18262 | License | MIT | Doc PR | - Commits ------- b9782b7 [Process] Accept Traversable input
2 parents 93e09fe + b9782b7 commit c61077c

File tree

5 files changed

+69
-18
lines changed

5 files changed

+69
-18
lines changed

src/Symfony/Component/Process/Pipes/AbstractPipes.php

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,15 @@ abstract class AbstractPipes implements PipesInterface
2222
public $pipes = array();
2323

2424
/** @var string */
25-
protected $inputBuffer = '';
26-
/** @var resource|null */
27-
protected $input;
28-
25+
private $inputBuffer = '';
26+
/** @var resource|\Iterator|null */
27+
private $input;
2928
/** @var bool */
3029
private $blocked = true;
3130

3231
public function __construct($input)
3332
{
34-
if (is_resource($input)) {
33+
if (is_resource($input) || $input instanceof \Iterator) {
3534
$this->input = $input;
3635
} elseif (is_string($input)) {
3736
$this->inputBuffer = $input;
@@ -76,7 +75,7 @@ protected function unblock()
7675
foreach ($this->pipes as $pipe) {
7776
stream_set_blocking($pipe, 0);
7877
}
79-
if (null !== $this->input) {
78+
if (is_resource($this->input)) {
8079
stream_set_blocking($this->input, 0);
8180
}
8281

@@ -91,9 +90,21 @@ protected function write()
9190
if (!isset($this->pipes[0])) {
9291
return;
9392
}
93+
$input = $this->input;
94+
95+
if ($input instanceof \Iterator) {
96+
if (!$input->valid()) {
97+
$input = null;
98+
} elseif (is_resource($input = $input->current())) {
99+
stream_set_blocking($input, 0);
100+
} else {
101+
$this->inputBuffer .= $input;
102+
$this->input->next();
103+
$input = null;
104+
}
105+
}
94106

95-
$e = array();
96-
$r = null !== $this->input ? array($this->input) : $e;
107+
$r = $e = array();
97108
$w = array($this->pipes[0]);
98109

99110
// let's have a look if something changed in streams
@@ -109,8 +120,7 @@ protected function write()
109120
return array($this->pipes[0]);
110121
}
111122
}
112-
113-
foreach ($r as $input) {
123+
if ($input) {
114124
for (;;) {
115125
$data = fread($input, self::CHUNK_SIZE);
116126
if (!isset($data[0])) {
@@ -124,16 +134,19 @@ protected function write()
124134
return array($this->pipes[0]);
125135
}
126136
}
127-
if (!isset($data[0]) && feof($input)) {
128-
// no more data to read on input resource
129-
// use an empty buffer in the next reads
130-
$this->input = null;
137+
if (feof($input)) {
138+
if ($this->input instanceof \Iterator) {
139+
$this->input->next();
140+
} else {
141+
$this->input = null;
142+
}
131143
}
132144
}
133145
}
134146

135147
// no input to read on resource, buffer is empty
136-
if (null === $this->input && !isset($this->inputBuffer[0])) {
148+
if (!isset($this->inputBuffer[0]) && !($this->input instanceof \Iterator ? $this->input->valid() : $this->input)) {
149+
$this->input = null;
137150
fclose($this->pipes[0]);
138151
unset($this->pipes[0]);
139152
}

src/Symfony/Component/Process/Process.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1200,6 +1200,9 @@ public function hasCallback()
12001200
*/
12011201
private function getDescriptors()
12021202
{
1203+
if ($this->input instanceof \Iterator) {
1204+
$this->input->rewind();
1205+
}
12031206
if ('\\' === DIRECTORY_SEPARATOR) {
12041207
$this->processPipes = WindowsPipes::create($this, $this->input);
12051208
} else {

src/Symfony/Component/Process/ProcessUtils.php

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,14 @@ public static function validateInput($caller, $input)
9393
if (is_scalar($input)) {
9494
return (string) $input;
9595
}
96+
if ($input instanceof \Iterator) {
97+
return $input;
98+
}
99+
if ($input instanceof \Traversable) {
100+
return new \IteratorIterator($input);
101+
}
96102

97-
throw new InvalidArgumentException(sprintf('%s only accepts strings or stream resources.', $caller));
103+
throw new InvalidArgumentException(sprintf('%s only accepts strings, Traversable objects or stream resources.', $caller));
98104
}
99105

100106
return $input;

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ public function testShouldReturnProcessWithEnabledOutput()
215215

216216
/**
217217
* @expectedException \Symfony\Component\Process\Exception\InvalidArgumentException
218-
* @expectedExceptionMessage Symfony\Component\Process\ProcessBuilder::setInput only accepts strings or stream resources.
218+
* @expectedExceptionMessage Symfony\Component\Process\ProcessBuilder::setInput only accepts strings, Traversable objects or stream resources.
219219
*/
220220
public function testInvalidInput()
221221
{

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

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ public function testSetInputWhileRunningThrowsAnException()
231231
/**
232232
* @dataProvider provideInvalidInputValues
233233
* @expectedException \Symfony\Component\Process\Exception\InvalidArgumentException
234-
* @expectedExceptionMessage Symfony\Component\Process\Process::setInput only accepts strings or stream resources.
234+
* @expectedExceptionMessage Symfony\Component\Process\Process::setInput only accepts strings, Traversable objects or stream resources.
235235
*/
236236
public function testInvalidInput($value)
237237
{
@@ -1156,6 +1156,35 @@ public function provideVariousIncrementals() {
11561156
);
11571157
}
11581158

1159+
public function testIteratorInput()
1160+
{
1161+
$nextData = 'ping';
1162+
$input = function () use (&$nextData) {
1163+
while (false !== $nextData) {
1164+
yield $nextData;
1165+
yield $nextData = '';
1166+
}
1167+
};
1168+
$input = $input();
1169+
1170+
$process = new Process(self::$phpBin.' -r '.escapeshellarg('stream_copy_to_stream(STDIN, STDOUT);'));
1171+
$process->setInput($input);
1172+
$process->start(function ($type, $data) use ($input, &$nextData) {
1173+
if ('ping' === $data) {
1174+
$h = fopen('php://memory', 'r+');
1175+
fwrite($h, 'pong');
1176+
rewind($h);
1177+
$nextData = $h;
1178+
$input->next();
1179+
} else {
1180+
$nextData = false;
1181+
}
1182+
});
1183+
1184+
$process->wait();
1185+
$this->assertSame('pingpong', $process->getOutput());
1186+
}
1187+
11591188
/**
11601189
* @param string $commandline
11611190
* @param null|string $cwd

0 commit comments

Comments
 (0)
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