diff --git a/src/StreamTrait.php b/src/StreamTrait.php index 079168d..f398dbc 100644 --- a/src/StreamTrait.php +++ b/src/StreamTrait.php @@ -17,7 +17,6 @@ use function ftell; use function fwrite; use function get_resource_type; -use function is_int; use function is_resource; use function is_string; use function stream_get_contents; @@ -38,6 +37,26 @@ trait StreamTrait */ private $resource; + /** + * @var int|null + */ + private ?int $size = null; + + /** + * @var bool|null + */ + private ?bool $seekable = null; + + /** + * @var bool|null + */ + private ?bool $writable = null; + + /** + * @var bool|null + */ + private ?bool $readable = null; + /** * Closes the stream and any underlying resources when the instance is destructed. */ @@ -90,7 +109,8 @@ public function close(): void public function detach() { $resource = $this->resource; - $this->resource = null; + $this->resource = $this->size = null; + $this->seekable = $this->writable = $this->readable = false; return $resource; } @@ -105,8 +125,12 @@ public function getSize(): ?int return null; } + if ($this->size !== null) { + return $this->size; + } + $stats = fstat($this->resource); - return isset($stats['size']) ? (int) $stats['size'] : null; + return $this->size = isset($stats['size']) ? (int) $stats['size'] : null; } /** @@ -121,7 +145,7 @@ public function tell(): int throw new RuntimeException('No resource available. Cannot tell position'); } - if (!is_int($result = ftell($this->resource))) { + if (($result = ftell($this->resource)) === false) { throw new RuntimeException('Error occurred during tell operation'); } @@ -145,7 +169,11 @@ public function eof(): bool */ public function isSeekable(): bool { - return ($this->resource && $this->getMetadata('seekable')); + if ($this->seekable !== null) { + return $this->seekable; + } + + return $this->seekable = ($this->resource && $this->getMetadata('seekable')); } /** @@ -198,11 +226,15 @@ public function rewind(): void */ public function isWritable(): bool { + if ($this->writable !== null) { + return $this->writable; + } + if (!is_string($mode = $this->getMetadata('mode'))) { - return false; + return $this->writable = false; } - return ( + return $this->writable = ( strpos($mode, 'w') !== false || strpos($mode, '+') !== false || strpos($mode, 'x') !== false @@ -228,7 +260,9 @@ public function write($string): int throw new RuntimeException('Stream is not writable.'); } - if (!is_int($result = fwrite($this->resource, $string))) { + $this->size = null; + + if (($result = fwrite($this->resource, $string)) === false) { throw new RuntimeException('Error writing to stream.'); } @@ -243,11 +277,15 @@ public function write($string): int */ public function isReadable(): bool { + if ($this->readable !== null) { + return $this->readable; + } + if (!is_string($mode = $this->getMetadata('mode'))) { - return false; + return $this->readable = false; } - return (strpos($mode, 'r') !== false || strpos($mode, '+') !== false); + return $this->readable = (strpos($mode, 'r') !== false || strpos($mode, '+') !== false); } /** @@ -270,7 +308,7 @@ public function read($length): string throw new RuntimeException('Stream is not readable.'); } - if (!is_string($result = fread($this->resource, $length))) { + if (($result = fread($this->resource, $length)) === false) { throw new RuntimeException('Error reading stream.'); } @@ -290,7 +328,7 @@ public function getContents(): string throw new RuntimeException('Stream is not readable.'); } - if (!is_string($result = stream_get_contents($this->resource))) { + if (($result = stream_get_contents($this->resource)) === false) { throw new RuntimeException('Error reading stream.'); } @@ -350,7 +388,7 @@ private function init($stream, string $mode): void if (!is_resource($stream) || get_resource_type($stream) !== 'stream') { throw new InvalidArgumentException( - 'Invalid stream provided. It must be a string stream identifier or stream resource.' + 'Invalid stream provided. It must be a string stream identifier or stream resource.', ); } diff --git a/tests/StreamTest.php b/tests/StreamTest.php index 13d3d2f..deafbec 100644 --- a/tests/StreamTest.php +++ b/tests/StreamTest.php @@ -106,6 +106,20 @@ public function testDetach(): void $this->assertNull($stream->getSize()); } + public function testWriteAndGetSizeAndDetach(): void + { + $this->assertSame(0, $this->stream->getSize()); + + $this->stream->write('a'); + $this->assertSame(1, $this->stream->getSize()); + + $this->stream->write('b'); + $this->assertSame(2, $this->stream->getSize()); + + $this->stream->detach(); + $this->assertNull($this->stream->getSize()); + } + public function testIsReadableReturnTrue(): void { $stream = new Stream($this->tmpFile, 'r'); @@ -197,4 +211,19 @@ public function testConstructorThrowExceptionForInvalidResource(): void $this->expectException(InvalidArgumentException::class); new Stream(stream_context_create(['phar' => ['compress' => Phar::GZ]])); } + + public function testCacheMetadataForCoverage(): void + { + $this->assertSame(0, $this->stream->getSize()); + $this->assertSame(0, $this->stream->getSize()); + + $this->assertTrue($this->stream->isReadable()); + $this->assertTrue($this->stream->isReadable()); + + $this->assertTrue($this->stream->isSeekable()); + $this->assertTrue($this->stream->isSeekable()); + + $this->assertTrue($this->stream->isWritable()); + $this->assertTrue($this->stream->isWritable()); + } }