Chris@0
|
1 <?php
|
Chris@0
|
2 namespace GuzzleHttp\Psr7;
|
Chris@0
|
3
|
Chris@0
|
4 use Psr\Http\Message\StreamInterface;
|
Chris@0
|
5
|
Chris@0
|
6 /**
|
Chris@0
|
7 * PHP stream implementation.
|
Chris@0
|
8 *
|
Chris@0
|
9 * @var $stream
|
Chris@0
|
10 */
|
Chris@0
|
11 class Stream implements StreamInterface
|
Chris@0
|
12 {
|
Chris@0
|
13 private $stream;
|
Chris@0
|
14 private $size;
|
Chris@0
|
15 private $seekable;
|
Chris@0
|
16 private $readable;
|
Chris@0
|
17 private $writable;
|
Chris@0
|
18 private $uri;
|
Chris@0
|
19 private $customMetadata;
|
Chris@0
|
20
|
Chris@0
|
21 /** @var array Hash of readable and writable stream types */
|
Chris@0
|
22 private static $readWriteHash = [
|
Chris@0
|
23 'read' => [
|
Chris@0
|
24 'r' => true, 'w+' => true, 'r+' => true, 'x+' => true, 'c+' => true,
|
Chris@0
|
25 'rb' => true, 'w+b' => true, 'r+b' => true, 'x+b' => true,
|
Chris@0
|
26 'c+b' => true, 'rt' => true, 'w+t' => true, 'r+t' => true,
|
Chris@0
|
27 'x+t' => true, 'c+t' => true, 'a+' => true
|
Chris@0
|
28 ],
|
Chris@0
|
29 'write' => [
|
Chris@0
|
30 'w' => true, 'w+' => true, 'rw' => true, 'r+' => true, 'x+' => true,
|
Chris@0
|
31 'c+' => true, 'wb' => true, 'w+b' => true, 'r+b' => true,
|
Chris@0
|
32 'x+b' => true, 'c+b' => true, 'w+t' => true, 'r+t' => true,
|
Chris@0
|
33 'x+t' => true, 'c+t' => true, 'a' => true, 'a+' => true
|
Chris@0
|
34 ]
|
Chris@0
|
35 ];
|
Chris@0
|
36
|
Chris@0
|
37 /**
|
Chris@0
|
38 * This constructor accepts an associative array of options.
|
Chris@0
|
39 *
|
Chris@0
|
40 * - size: (int) If a read stream would otherwise have an indeterminate
|
Chris@0
|
41 * size, but the size is known due to foreknowledge, then you can
|
Chris@0
|
42 * provide that size, in bytes.
|
Chris@0
|
43 * - metadata: (array) Any additional metadata to return when the metadata
|
Chris@0
|
44 * of the stream is accessed.
|
Chris@0
|
45 *
|
Chris@0
|
46 * @param resource $stream Stream resource to wrap.
|
Chris@0
|
47 * @param array $options Associative array of options.
|
Chris@0
|
48 *
|
Chris@0
|
49 * @throws \InvalidArgumentException if the stream is not a stream resource
|
Chris@0
|
50 */
|
Chris@0
|
51 public function __construct($stream, $options = [])
|
Chris@0
|
52 {
|
Chris@0
|
53 if (!is_resource($stream)) {
|
Chris@0
|
54 throw new \InvalidArgumentException('Stream must be a resource');
|
Chris@0
|
55 }
|
Chris@0
|
56
|
Chris@0
|
57 if (isset($options['size'])) {
|
Chris@0
|
58 $this->size = $options['size'];
|
Chris@0
|
59 }
|
Chris@0
|
60
|
Chris@0
|
61 $this->customMetadata = isset($options['metadata'])
|
Chris@0
|
62 ? $options['metadata']
|
Chris@0
|
63 : [];
|
Chris@0
|
64
|
Chris@0
|
65 $this->stream = $stream;
|
Chris@0
|
66 $meta = stream_get_meta_data($this->stream);
|
Chris@0
|
67 $this->seekable = $meta['seekable'];
|
Chris@0
|
68 $this->readable = isset(self::$readWriteHash['read'][$meta['mode']]);
|
Chris@0
|
69 $this->writable = isset(self::$readWriteHash['write'][$meta['mode']]);
|
Chris@0
|
70 $this->uri = $this->getMetadata('uri');
|
Chris@0
|
71 }
|
Chris@0
|
72
|
Chris@0
|
73 public function __get($name)
|
Chris@0
|
74 {
|
Chris@0
|
75 if ($name == 'stream') {
|
Chris@0
|
76 throw new \RuntimeException('The stream is detached');
|
Chris@0
|
77 }
|
Chris@0
|
78
|
Chris@0
|
79 throw new \BadMethodCallException('No value for ' . $name);
|
Chris@0
|
80 }
|
Chris@0
|
81
|
Chris@0
|
82 /**
|
Chris@0
|
83 * Closes the stream when the destructed
|
Chris@0
|
84 */
|
Chris@0
|
85 public function __destruct()
|
Chris@0
|
86 {
|
Chris@0
|
87 $this->close();
|
Chris@0
|
88 }
|
Chris@0
|
89
|
Chris@0
|
90 public function __toString()
|
Chris@0
|
91 {
|
Chris@0
|
92 try {
|
Chris@0
|
93 $this->seek(0);
|
Chris@0
|
94 return (string) stream_get_contents($this->stream);
|
Chris@0
|
95 } catch (\Exception $e) {
|
Chris@0
|
96 return '';
|
Chris@0
|
97 }
|
Chris@0
|
98 }
|
Chris@0
|
99
|
Chris@0
|
100 public function getContents()
|
Chris@0
|
101 {
|
Chris@0
|
102 $contents = stream_get_contents($this->stream);
|
Chris@0
|
103
|
Chris@0
|
104 if ($contents === false) {
|
Chris@0
|
105 throw new \RuntimeException('Unable to read stream contents');
|
Chris@0
|
106 }
|
Chris@0
|
107
|
Chris@0
|
108 return $contents;
|
Chris@0
|
109 }
|
Chris@0
|
110
|
Chris@0
|
111 public function close()
|
Chris@0
|
112 {
|
Chris@0
|
113 if (isset($this->stream)) {
|
Chris@0
|
114 if (is_resource($this->stream)) {
|
Chris@0
|
115 fclose($this->stream);
|
Chris@0
|
116 }
|
Chris@0
|
117 $this->detach();
|
Chris@0
|
118 }
|
Chris@0
|
119 }
|
Chris@0
|
120
|
Chris@0
|
121 public function detach()
|
Chris@0
|
122 {
|
Chris@0
|
123 if (!isset($this->stream)) {
|
Chris@0
|
124 return null;
|
Chris@0
|
125 }
|
Chris@0
|
126
|
Chris@0
|
127 $result = $this->stream;
|
Chris@0
|
128 unset($this->stream);
|
Chris@0
|
129 $this->size = $this->uri = null;
|
Chris@0
|
130 $this->readable = $this->writable = $this->seekable = false;
|
Chris@0
|
131
|
Chris@0
|
132 return $result;
|
Chris@0
|
133 }
|
Chris@0
|
134
|
Chris@0
|
135 public function getSize()
|
Chris@0
|
136 {
|
Chris@0
|
137 if ($this->size !== null) {
|
Chris@0
|
138 return $this->size;
|
Chris@0
|
139 }
|
Chris@0
|
140
|
Chris@0
|
141 if (!isset($this->stream)) {
|
Chris@0
|
142 return null;
|
Chris@0
|
143 }
|
Chris@0
|
144
|
Chris@0
|
145 // Clear the stat cache if the stream has a URI
|
Chris@0
|
146 if ($this->uri) {
|
Chris@0
|
147 clearstatcache(true, $this->uri);
|
Chris@0
|
148 }
|
Chris@0
|
149
|
Chris@0
|
150 $stats = fstat($this->stream);
|
Chris@0
|
151 if (isset($stats['size'])) {
|
Chris@0
|
152 $this->size = $stats['size'];
|
Chris@0
|
153 return $this->size;
|
Chris@0
|
154 }
|
Chris@0
|
155
|
Chris@0
|
156 return null;
|
Chris@0
|
157 }
|
Chris@0
|
158
|
Chris@0
|
159 public function isReadable()
|
Chris@0
|
160 {
|
Chris@0
|
161 return $this->readable;
|
Chris@0
|
162 }
|
Chris@0
|
163
|
Chris@0
|
164 public function isWritable()
|
Chris@0
|
165 {
|
Chris@0
|
166 return $this->writable;
|
Chris@0
|
167 }
|
Chris@0
|
168
|
Chris@0
|
169 public function isSeekable()
|
Chris@0
|
170 {
|
Chris@0
|
171 return $this->seekable;
|
Chris@0
|
172 }
|
Chris@0
|
173
|
Chris@0
|
174 public function eof()
|
Chris@0
|
175 {
|
Chris@0
|
176 return !$this->stream || feof($this->stream);
|
Chris@0
|
177 }
|
Chris@0
|
178
|
Chris@0
|
179 public function tell()
|
Chris@0
|
180 {
|
Chris@0
|
181 $result = ftell($this->stream);
|
Chris@0
|
182
|
Chris@0
|
183 if ($result === false) {
|
Chris@0
|
184 throw new \RuntimeException('Unable to determine stream position');
|
Chris@0
|
185 }
|
Chris@0
|
186
|
Chris@0
|
187 return $result;
|
Chris@0
|
188 }
|
Chris@0
|
189
|
Chris@0
|
190 public function rewind()
|
Chris@0
|
191 {
|
Chris@0
|
192 $this->seek(0);
|
Chris@0
|
193 }
|
Chris@0
|
194
|
Chris@0
|
195 public function seek($offset, $whence = SEEK_SET)
|
Chris@0
|
196 {
|
Chris@0
|
197 if (!$this->seekable) {
|
Chris@0
|
198 throw new \RuntimeException('Stream is not seekable');
|
Chris@0
|
199 } elseif (fseek($this->stream, $offset, $whence) === -1) {
|
Chris@0
|
200 throw new \RuntimeException('Unable to seek to stream position '
|
Chris@0
|
201 . $offset . ' with whence ' . var_export($whence, true));
|
Chris@0
|
202 }
|
Chris@0
|
203 }
|
Chris@0
|
204
|
Chris@0
|
205 public function read($length)
|
Chris@0
|
206 {
|
Chris@0
|
207 if (!$this->readable) {
|
Chris@0
|
208 throw new \RuntimeException('Cannot read from non-readable stream');
|
Chris@0
|
209 }
|
Chris@0
|
210 if ($length < 0) {
|
Chris@0
|
211 throw new \RuntimeException('Length parameter cannot be negative');
|
Chris@0
|
212 }
|
Chris@0
|
213
|
Chris@0
|
214 if (0 === $length) {
|
Chris@0
|
215 return '';
|
Chris@0
|
216 }
|
Chris@0
|
217
|
Chris@0
|
218 $string = fread($this->stream, $length);
|
Chris@0
|
219 if (false === $string) {
|
Chris@0
|
220 throw new \RuntimeException('Unable to read from stream');
|
Chris@0
|
221 }
|
Chris@0
|
222
|
Chris@0
|
223 return $string;
|
Chris@0
|
224 }
|
Chris@0
|
225
|
Chris@0
|
226 public function write($string)
|
Chris@0
|
227 {
|
Chris@0
|
228 if (!$this->writable) {
|
Chris@0
|
229 throw new \RuntimeException('Cannot write to a non-writable stream');
|
Chris@0
|
230 }
|
Chris@0
|
231
|
Chris@0
|
232 // We can't know the size after writing anything
|
Chris@0
|
233 $this->size = null;
|
Chris@0
|
234 $result = fwrite($this->stream, $string);
|
Chris@0
|
235
|
Chris@0
|
236 if ($result === false) {
|
Chris@0
|
237 throw new \RuntimeException('Unable to write to stream');
|
Chris@0
|
238 }
|
Chris@0
|
239
|
Chris@0
|
240 return $result;
|
Chris@0
|
241 }
|
Chris@0
|
242
|
Chris@0
|
243 public function getMetadata($key = null)
|
Chris@0
|
244 {
|
Chris@0
|
245 if (!isset($this->stream)) {
|
Chris@0
|
246 return $key ? null : [];
|
Chris@0
|
247 } elseif (!$key) {
|
Chris@0
|
248 return $this->customMetadata + stream_get_meta_data($this->stream);
|
Chris@0
|
249 } elseif (isset($this->customMetadata[$key])) {
|
Chris@0
|
250 return $this->customMetadata[$key];
|
Chris@0
|
251 }
|
Chris@0
|
252
|
Chris@0
|
253 $meta = stream_get_meta_data($this->stream);
|
Chris@0
|
254
|
Chris@0
|
255 return isset($meta[$key]) ? $meta[$key] : null;
|
Chris@0
|
256 }
|
Chris@0
|
257 }
|