Skip to content

Commit fefce50

Browse files
committed
:octocat: +StreamUtil::copyToStream()
1 parent 38ab0ef commit fefce50

File tree

2 files changed

+89
-1
lines changed

2 files changed

+89
-1
lines changed

src/StreamUtil.php

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,13 @@
1212

1313
use InvalidArgumentException;
1414
use Psr\Http\Message\StreamInterface;
15+
use RuntimeException;
1516
use Throwable;
1617
use function in_array;
18+
use function min;
1719
use function preg_match;
1820
use function str_contains;
21+
use function strlen;
1922
use function substr;
2023

2124
/**
@@ -130,4 +133,37 @@ public static function getContents(StreamInterface $stream):?string{
130133
return $data;
131134
}
132135

136+
/**
137+
* Copies a stream to another stream, starting from the current position of the source stream,
138+
* reading to the end or until the given maxlength is hit.
139+
*
140+
* Throws if the source is not readable or the destination not writable.
141+
*
142+
* @throws \RuntimeException
143+
*/
144+
public static function copyToStream(StreamInterface $source, StreamInterface $destination, int $maxLength = null):int{
145+
146+
if(!$source->isReadable() || !$destination->isWritable()){
147+
throw new RuntimeException('$source must be readable and $destination must be writable');
148+
}
149+
150+
$remaining = ($maxLength ?? ($source->getSize() - $source->tell()));
151+
$bytesRead = 0;
152+
153+
while($remaining > 0 && !$source->eof()){
154+
$chunk = $source->read(min(8192, $remaining));
155+
$length = strlen($chunk);
156+
$bytesRead += $length;
157+
158+
if($length === 0){
159+
break;
160+
}
161+
162+
$remaining -= $length;
163+
$destination->write($chunk);
164+
}
165+
166+
return $bytesRead;
167+
}
168+
133169
}

tests/StreamUtilTest.php

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,12 @@
1313
use chillerlan\HTTP\Utils\StreamUtil;
1414
use InvalidArgumentException;
1515
use PHPUnit\Framework\TestCase;
16+
use RuntimeException;
1617
use function fclose;
1718
use function fopen;
1819
use function stream_get_meta_data;
20+
use function strlen;
1921
use function substr;
20-
use function var_dump;
2122

2223
/**
2324
*
@@ -85,4 +86,55 @@ public function testGetContentsFromUnreadableStream():void{
8586
$this::assertNull(StreamUtil::getContents($stream));
8687
}
8788

89+
public function testCopyToStream():void{
90+
$content = 'teststream';
91+
92+
$streamA = $this->streamFactory->createStream($content);
93+
$streamB = $this->streamFactory->createStream();
94+
95+
$bytesRead = StreamUtil::copyToStream($streamA, $streamB);
96+
97+
$this::assertSame(strlen($content), $bytesRead);
98+
$this::assertSame($content, (string)$streamB); // -> "teststream"
99+
}
100+
101+
public function testCopyToStreamWithMaxlength():void{
102+
$content = 'teststream';
103+
$maxlength = 4;
104+
105+
$streamA = $this->streamFactory->createStream($content);
106+
$streamB = $this->streamFactory->createStream();
107+
108+
$bytesRead = StreamUtil::copyToStream($streamA, $streamB, $maxlength);
109+
110+
$this::assertSame($maxlength, $bytesRead);
111+
$this::assertSame(substr($content, 0, $maxlength), (string)$streamB); // -> "test"
112+
}
113+
114+
public function testCopyToStreamFromCurrentPosition():void{
115+
$content = 'teststream';
116+
$position = 4;
117+
118+
$streamA = $this->streamFactory->createStream($content);
119+
$streamB = $this->streamFactory->createStream();
120+
121+
$streamA->seek($position);
122+
$pos = $streamA->tell();
123+
124+
$bytesRead = StreamUtil::copyToStream($streamA, $streamB);
125+
126+
$this::assertSame((strlen($content) - $pos), $bytesRead);
127+
$this::assertSame(substr($content, $position), (string)$streamB); // -> "stream"
128+
}
129+
130+
public function testCopyToStreamException():void{
131+
$this->expectException(RuntimeException::class);
132+
$this->expectExceptionMessage('$source must be readable and $destination must be writable');
133+
134+
$streamA = $this->streamFactory->createStreamFromResource(fopen(__DIR__.'/fopen-test.txt', 'a'));
135+
$streamB = $this->streamFactory->createStream();
136+
137+
StreamUtil::copyToStream($streamA, $streamB);
138+
}
139+
88140
}

0 commit comments

Comments
 (0)