* Author: Martijn van der Ven . * @license https://github.com/Nyholm/psr7/blob/master/LICENSE */ final class StandardStream implements StreamInterface { /** @var array Hash of readable and writable stream types */ private const READ_WRITE_HASH = [ 'read' => [ 'r' => true, 'w+' => true, 'r+' => true, 'x+' => true, 'c+' => true, 'rb' => true, 'w+b' => true, 'r+b' => true, 'x+b' => true, 'c+b' => true, 'rt' => true, 'w+t' => true, 'r+t' => true, 'x+t' => true, 'c+t' => true, 'a+' => true, ], 'write' => [ 'w' => true, 'w+' => true, 'rw' => true, 'r+' => true, 'x+' => true, 'c+' => true, 'wb' => true, 'w+b' => true, 'r+b' => true, 'x+b' => true, 'c+b' => true, 'w+t' => true, 'r+t' => true, 'x+t' => true, 'c+t' => true, 'a' => true, 'a+' => true, ], ]; /** @var null|resource A resource reference */ private $stream; /** @var bool */ private $seekable; /** @var bool */ private $readable; /** @var bool */ private $writable; /** @var null|array|mixed|void */ private $uri; /** @var null|int */ private $size; private function __construct() { } /** * Closes the stream when the destructed. */ public function __destruct() { $this->close(); } public function __toString(): string { try { if ($this->isSeekable()) { $this->seek(0); } return $this->getContents(); } catch (Exception $e) { return ''; } } /** * Creates a new PSR-7 stream. * * @param resource|StreamInterface|string $body * * @throws InvalidArgumentException */ public static function create($body = ''): StreamInterface { if ($body instanceof StreamInterface) { return $body; } if (is_string($body)) { $resource = fopen('php://temp', 'rw+'); fwrite($resource, $body); $body = $resource; } if (is_resource($body)) { $new = new self(); $new->stream = $body; $meta = stream_get_meta_data($new->stream); $new->seekable = $meta['seekable'] && fseek($new->stream, 0, SEEK_CUR) === 0; $new->readable = isset(self::READ_WRITE_HASH['read'][$meta['mode']]); $new->writable = isset(self::READ_WRITE_HASH['write'][$meta['mode']]); $new->uri = $new->getMetadata('uri'); return $new; } throw new InvalidArgumentException('First argument to Stream::create() must be a string, resource or StreamInterface.'); } public function close(): void { if (isset($this->stream)) { if (is_resource($this->stream)) { fclose($this->stream); } $this->detach(); } } public function detach() { if (! isset($this->stream)) { return null; } $result = $this->stream; unset($this->stream); $this->size = $this->uri = null; $this->readable = $this->writable = $this->seekable = false; return $result; } public function getSize(): ?int { if ($this->size !== null) { return $this->size; } if (! isset($this->stream)) { return null; } // Clear the stat cache if the stream has a URI if ($this->uri) { clearstatcache(true, $this->uri); } $stats = fstat($this->stream); if (isset($stats['size'])) { $this->size = $stats['size']; return $this->size; } return null; } public function tell(): int { if (false === $result = ftell($this->stream)) { throw new RuntimeException('Unable to determine stream position'); } return $result; } public function eof(): bool { return ! $this->stream || feof($this->stream); } public function isSeekable(): bool { return $this->seekable; } public function seek($offset, $whence = SEEK_SET): void { if (! $this->seekable) { throw new RuntimeException('Stream is not seekable'); } if (fseek($this->stream, $offset, $whence) === -1) { throw new RuntimeException('Unable to seek to stream position ' . $offset . ' with whence ' . var_export($whence, true)); } } public function rewind(): void { $this->seek(0); } public function isWritable(): bool { return $this->writable; } public function write($string): int { if (! $this->writable) { throw new RuntimeException('Cannot write to a non-writable stream'); } // We can't know the size after writing anything $this->size = null; if (false === $result = fwrite($this->stream, $string)) { throw new RuntimeException('Unable to write to stream'); } return $result; } public function isReadable(): bool { return $this->readable; } public function read($length): string { if (! $this->readable) { throw new RuntimeException('Cannot read from non-readable stream'); } return fread($this->stream, $length); } public function getContents(): string { if (! isset($this->stream)) { throw new RuntimeException('Unable to read stream contents'); } if (false === $contents = stream_get_contents($this->stream)) { throw new RuntimeException('Unable to read stream contents'); } return $contents; } public function getMetadata($key = null) { if (! isset($this->stream)) { return $key ? null : []; } $meta = stream_get_meta_data($this->stream); if ($key === null) { return $meta; } return $meta[$key] ?? null; } }