Skip to content

Commit

Permalink
Use zval storage for Stream instances created from strings
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolas-grekas committed Apr 17, 2023
1 parent 07c455a commit d978d61
Show file tree
Hide file tree
Showing 2 changed files with 102 additions and 5 deletions.
105 changes: 101 additions & 4 deletions src/Stream.php
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,7 @@ public static function create($body = ''): StreamInterface
}

if (\is_string($body)) {
$resource = \fopen('php://temp', 'rw+');
\fwrite($resource, $body);
\fseek($resource, 0);
$body = $resource;
$body = self::openZvalStream($body);
}

if (!\is_resource($body)) {
Expand Down Expand Up @@ -313,4 +310,104 @@ public function getMetadata($key = null)

return $meta[$key] ?? null;
}

private static function openZvalStream(string $body)
{
static $wrapper;

$wrapper ?? \stream_wrapper_register('Nyholm-Psr7-Zval', $wrapper = \get_class(new class() {
public $context;

private $data;
private $position = 0;

public function stream_open(): bool
{
$this->data = \stream_context_get_options($this->context)['Nyholm-Psr7-Zval']['data'];
\stream_context_set_option($this->context, 'Nyholm-Psr7-Zval', 'data', null);

return true;
}

public function stream_read(int $count): string
{
$result = \substr($this->data, $this->position, $count);
$this->position += \strlen($result);

return $result;
}

public function stream_write(string $data): int
{
$this->data = \substr_replace($this->data, $data, $this->position, \strlen($data));
$this->position += \strlen($data);

return \strlen($data);
}

public function stream_tell(): int
{
return $this->position;
}

public function stream_eof(): bool
{
return \strlen($this->data) <= $this->position;
}

public function stream_stat(): array
{
return [
'mode' => 33206, // POSIX_S_IFREG | 0666
'nlink' => 1,
'rdev' => -1,
'size' => \strlen($this->data),
'blksize' => -1,
'blocks' => -1,
];
}

public function stream_seek(int $offset, int $whence): bool
{
if (\SEEK_SET === $whence && (0 <= $offset && \strlen($this->data) >= $offset)) {
$this->position = $offset;
} elseif (\SEEK_CUR === $whence && 0 <= $offset) {
$this->position += $offset;
} elseif (\SEEK_END === $whence && (0 > $offset && 0 <= $offset = \strlen($this->data) + $offset)) {
$this->position = $offset;
} else {
return false;
}

return true;
}

public function stream_set_option(): bool
{
return true;
}

public function stream_truncate(int $new_size): bool
{
if ($new_size) {
$this->data = \substr($this->data, 0, $new_size);
$this->position = \min($this->position, $new_size);
} else {
$this->data = '';
$this->position = 0;
}

return true;
}
}));

$context = \stream_context_create(['Nyholm-Psr7-Zval' => ['data' => $body]]);

if (!$stream = @\fopen('Nyholm-Psr7-Zval://', 'r+', false, $context)) {
\stream_wrapper_register('Nyholm-Psr7-Zval', $wrapper);
$stream = \fopen('Nyholm-Psr7-Zval://', 'r+', false, $context);
}

return $stream;
}
}
2 changes: 1 addition & 1 deletion tests/StreamTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public function testConstructorSeekWithStringContent()
$this->assertTrue($stream->isReadable());
$this->assertTrue($stream->isWritable());
$this->assertTrue($stream->isSeekable());
$this->assertEquals('php://temp', $stream->getMetadata('uri'));
$this->assertEquals('Nyholm-Psr7-Zval://', $stream->getMetadata('uri'));
$this->assertTrue(\is_array($stream->getMetadata()));
$this->assertSame(5, $stream->getSize());
$this->assertFalse($stream->eof());
Expand Down

0 comments on commit d978d61

Please sign in to comment.