diff --git a/composer.json b/composer.json index a676df5..470c937 100644 --- a/composer.json +++ b/composer.json @@ -43,7 +43,7 @@ }, "extra": { "branch-alias": { - "dev-master": "1.6-dev" + "dev-master": "1.7-dev" } } } diff --git a/src/Stream.php b/src/Stream.php index 9ed9bec..65682ac 100644 --- a/src/Stream.php +++ b/src/Stream.php @@ -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)) { @@ -284,4 +281,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; + } } diff --git a/tests/StreamTest.php b/tests/StreamTest.php index c9973da..8f9c5bf 100644 --- a/tests/StreamTest.php +++ b/tests/StreamTest.php @@ -33,7 +33,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());