stream = $stream; $this->size = $stream->getSize(); if ($useFileBuffer) { $this->resource = \fopen('php://temp/maxmemory:' . $memoryBuffer, 'rw+'); } else { $this->resource = \fopen('php://memory', 'rw+'); } if (\false === $this->resource) { throw new \RuntimeException('Cannot create a resource over temp or memory implementation'); } } public function __toString() : string { try { $this->rewind(); return $this->getContents(); } catch (\Throwable $throwable) { return ''; } } public function close() : void { if (null === $this->resource) { throw new \RuntimeException('Cannot close on a detached stream'); } $this->stream->close(); \fclose($this->resource); } public function detach() { if (null === $this->resource) { return null; } // Force reading the remaining data of the stream $this->getContents(); $resource = $this->resource; $this->stream->close(); $this->stream = null; $this->resource = null; return $resource; } public function getSize() : ?int { if (null === $this->resource) { return null; } if (null === $this->size && $this->stream->eof()) { return $this->written; } return $this->size; } public function tell() : int { if (null === $this->resource) { throw new \RuntimeException('Cannot tell on a detached stream'); } $tell = \ftell($this->resource); if (\false === $tell) { throw new \RuntimeException('ftell failed'); } return $tell; } public function eof() : bool { if (null === $this->resource) { throw new \RuntimeException('Cannot call eof on a detached stream'); } // We are at the end only when both our resource and underlying stream are at eof return $this->stream->eof() && \ftell($this->resource) === $this->written; } public function isSeekable() : bool { return null !== $this->resource; } public function seek(int $offset, int $whence = \SEEK_SET) : void { if (null === $this->resource) { throw new \RuntimeException('Cannot seek on a detached stream'); } \fseek($this->resource, $offset, $whence); } public function rewind() : void { if (null === $this->resource) { throw new \RuntimeException('Cannot rewind on a detached stream'); } \rewind($this->resource); } public function isWritable() : bool { return \false; } public function write(string $string) : int { throw new \RuntimeException('Cannot write on this stream'); } public function isReadable() : bool { return null !== $this->resource; } public function read(int $length) : string { if (null === $this->resource) { throw new \RuntimeException('Cannot read on a detached stream'); } if ($length < 0) { throw new \InvalidArgumentException('Can not read a negative amount of bytes'); } $read = ''; // First read from the resource if (\ftell($this->resource) !== $this->written) { $read = \fread($this->resource, $length); } if (\false === $read) { throw new \RuntimeException('Failed to read from resource'); } $bytesRead = \strlen($read); if ($bytesRead < $length) { $streamRead = $this->stream->read($length - $bytesRead); // Write on the underlying stream what we read $this->written += \fwrite($this->resource, $streamRead); $read .= $streamRead; } return $read; } public function getContents() : string { if (null === $this->resource) { throw new \RuntimeException('Cannot read on a detached stream'); } $read = ''; while (!$this->eof()) { $read .= $this->read(8192); } return $read; } public function getMetadata(?string $key = null) { if (null === $this->resource) { if (null === $key) { return []; } return null; } $metadata = \stream_get_meta_data($this->resource); if (null === $key) { return $metadata; } if (!\array_key_exists($key, $metadata)) { return null; } return $metadata[$key]; } }