2 namespace GuzzleHttp\Psr7;
4 use Psr\Http\Message\StreamInterface;
7 * PHP stream implementation.
11 class Stream implements StreamInterface
19 private $customMetadata;
21 /** @var array Hash of readable and writable stream types */
22 private static $readWriteHash = [
24 'r' => true, 'w+' => true, 'r+' => true, 'x+' => true, 'c+' => true,
25 'rb' => true, 'w+b' => true, 'r+b' => true, 'x+b' => true,
26 'c+b' => true, 'rt' => true, 'w+t' => true, 'r+t' => true,
27 'x+t' => true, 'c+t' => true, 'a+' => true
30 'w' => true, 'w+' => true, 'rw' => true, 'r+' => true, 'x+' => true,
31 'c+' => true, 'wb' => true, 'w+b' => true, 'r+b' => true,
32 'x+b' => true, 'c+b' => true, 'w+t' => true, 'r+t' => true,
33 'x+t' => true, 'c+t' => true, 'a' => true, 'a+' => true
38 * This constructor accepts an associative array of options.
40 * - size: (int) If a read stream would otherwise have an indeterminate
41 * size, but the size is known due to foreknowledge, then you can
42 * provide that size, in bytes.
43 * - metadata: (array) Any additional metadata to return when the metadata
44 * of the stream is accessed.
46 * @param resource $stream Stream resource to wrap.
47 * @param array $options Associative array of options.
49 * @throws \InvalidArgumentException if the stream is not a stream resource
51 public function __construct($stream, $options = [])
53 if (!is_resource($stream)) {
54 throw new \InvalidArgumentException('Stream must be a resource');
57 if (isset($options['size'])) {
58 $this->size = $options['size'];
61 $this->customMetadata = isset($options['metadata'])
62 ? $options['metadata']
65 $this->stream = $stream;
66 $meta = stream_get_meta_data($this->stream);
67 $this->seekable = $meta['seekable'];
68 $this->readable = isset(self::$readWriteHash['read'][$meta['mode']]);
69 $this->writable = isset(self::$readWriteHash['write'][$meta['mode']]);
70 $this->uri = $this->getMetadata('uri');
73 public function __get($name)
75 if ($name == 'stream') {
76 throw new \RuntimeException('The stream is detached');
79 throw new \BadMethodCallException('No value for ' . $name);
83 * Closes the stream when the destructed
85 public function __destruct()
90 public function __toString()
94 return (string) stream_get_contents($this->stream);
95 } catch (\Exception $e) {
100 public function getContents()
102 $contents = stream_get_contents($this->stream);
104 if ($contents === false) {
105 throw new \RuntimeException('Unable to read stream contents');
111 public function close()
113 if (isset($this->stream)) {
114 if (is_resource($this->stream)) {
115 fclose($this->stream);
121 public function detach()
123 if (!isset($this->stream)) {
127 $result = $this->stream;
128 unset($this->stream);
129 $this->size = $this->uri = null;
130 $this->readable = $this->writable = $this->seekable = false;
135 public function getSize()
137 if ($this->size !== null) {
141 if (!isset($this->stream)) {
145 // Clear the stat cache if the stream has a URI
147 clearstatcache(true, $this->uri);
150 $stats = fstat($this->stream);
151 if (isset($stats['size'])) {
152 $this->size = $stats['size'];
159 public function isReadable()
161 return $this->readable;
164 public function isWritable()
166 return $this->writable;
169 public function isSeekable()
171 return $this->seekable;
174 public function eof()
176 return !$this->stream || feof($this->stream);
179 public function tell()
181 $result = ftell($this->stream);
183 if ($result === false) {
184 throw new \RuntimeException('Unable to determine stream position');
190 public function rewind()
195 public function seek($offset, $whence = SEEK_SET)
197 if (!$this->seekable) {
198 throw new \RuntimeException('Stream is not seekable');
199 } elseif (fseek($this->stream, $offset, $whence) === -1) {
200 throw new \RuntimeException('Unable to seek to stream position '
201 . $offset . ' with whence ' . var_export($whence, true));
205 public function read($length)
207 if (!$this->readable) {
208 throw new \RuntimeException('Cannot read from non-readable stream');
211 throw new \RuntimeException('Length parameter cannot be negative');
218 $string = fread($this->stream, $length);
219 if (false === $string) {
220 throw new \RuntimeException('Unable to read from stream');
226 public function write($string)
228 if (!$this->writable) {
229 throw new \RuntimeException('Cannot write to a non-writable stream');
232 // We can't know the size after writing anything
234 $result = fwrite($this->stream, $string);
236 if ($result === false) {
237 throw new \RuntimeException('Unable to write to stream');
243 public function getMetadata($key = null)
245 if (!isset($this->stream)) {
246 return $key ? null : [];
248 return $this->customMetadata + stream_get_meta_data($this->stream);
249 } elseif (isset($this->customMetadata[$key])) {
250 return $this->customMetadata[$key];
253 $meta = stream_get_meta_data($this->stream);
255 return isset($meta[$key]) ? $meta[$key] : null;