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@17
|
27 'x+t' => true, 'c+t' => true, 'a+' => true, 'rb+' => true,
|
Chris@0
|
28 ],
|
Chris@0
|
29 'write' => [
|
Chris@0
|
30 'w' => true, 'w+' => true, 'rw' => true, 'r+' => true, 'x+' => true,
|
Chris@17
|
31 'c+' => true, 'wb' => true, 'w+b' => true, 'r+b' => true, 'rb+' => 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 /**
|
Chris@0
|
74 * Closes the stream when the destructed
|
Chris@0
|
75 */
|
Chris@0
|
76 public function __destruct()
|
Chris@0
|
77 {
|
Chris@0
|
78 $this->close();
|
Chris@0
|
79 }
|
Chris@0
|
80
|
Chris@0
|
81 public function __toString()
|
Chris@0
|
82 {
|
Chris@0
|
83 try {
|
Chris@0
|
84 $this->seek(0);
|
Chris@0
|
85 return (string) stream_get_contents($this->stream);
|
Chris@0
|
86 } catch (\Exception $e) {
|
Chris@0
|
87 return '';
|
Chris@0
|
88 }
|
Chris@0
|
89 }
|
Chris@0
|
90
|
Chris@0
|
91 public function getContents()
|
Chris@0
|
92 {
|
Chris@17
|
93 if (!isset($this->stream)) {
|
Chris@17
|
94 throw new \RuntimeException('Stream is detached');
|
Chris@17
|
95 }
|
Chris@17
|
96
|
Chris@0
|
97 $contents = stream_get_contents($this->stream);
|
Chris@0
|
98
|
Chris@0
|
99 if ($contents === false) {
|
Chris@0
|
100 throw new \RuntimeException('Unable to read stream contents');
|
Chris@0
|
101 }
|
Chris@0
|
102
|
Chris@0
|
103 return $contents;
|
Chris@0
|
104 }
|
Chris@0
|
105
|
Chris@0
|
106 public function close()
|
Chris@0
|
107 {
|
Chris@0
|
108 if (isset($this->stream)) {
|
Chris@0
|
109 if (is_resource($this->stream)) {
|
Chris@0
|
110 fclose($this->stream);
|
Chris@0
|
111 }
|
Chris@0
|
112 $this->detach();
|
Chris@0
|
113 }
|
Chris@0
|
114 }
|
Chris@0
|
115
|
Chris@0
|
116 public function detach()
|
Chris@0
|
117 {
|
Chris@0
|
118 if (!isset($this->stream)) {
|
Chris@0
|
119 return null;
|
Chris@0
|
120 }
|
Chris@0
|
121
|
Chris@0
|
122 $result = $this->stream;
|
Chris@0
|
123 unset($this->stream);
|
Chris@0
|
124 $this->size = $this->uri = null;
|
Chris@0
|
125 $this->readable = $this->writable = $this->seekable = false;
|
Chris@0
|
126
|
Chris@0
|
127 return $result;
|
Chris@0
|
128 }
|
Chris@0
|
129
|
Chris@0
|
130 public function getSize()
|
Chris@0
|
131 {
|
Chris@0
|
132 if ($this->size !== null) {
|
Chris@0
|
133 return $this->size;
|
Chris@0
|
134 }
|
Chris@0
|
135
|
Chris@0
|
136 if (!isset($this->stream)) {
|
Chris@0
|
137 return null;
|
Chris@0
|
138 }
|
Chris@0
|
139
|
Chris@0
|
140 // Clear the stat cache if the stream has a URI
|
Chris@0
|
141 if ($this->uri) {
|
Chris@0
|
142 clearstatcache(true, $this->uri);
|
Chris@0
|
143 }
|
Chris@0
|
144
|
Chris@0
|
145 $stats = fstat($this->stream);
|
Chris@0
|
146 if (isset($stats['size'])) {
|
Chris@0
|
147 $this->size = $stats['size'];
|
Chris@0
|
148 return $this->size;
|
Chris@0
|
149 }
|
Chris@0
|
150
|
Chris@0
|
151 return null;
|
Chris@0
|
152 }
|
Chris@0
|
153
|
Chris@0
|
154 public function isReadable()
|
Chris@0
|
155 {
|
Chris@0
|
156 return $this->readable;
|
Chris@0
|
157 }
|
Chris@0
|
158
|
Chris@0
|
159 public function isWritable()
|
Chris@0
|
160 {
|
Chris@0
|
161 return $this->writable;
|
Chris@0
|
162 }
|
Chris@0
|
163
|
Chris@0
|
164 public function isSeekable()
|
Chris@0
|
165 {
|
Chris@0
|
166 return $this->seekable;
|
Chris@0
|
167 }
|
Chris@0
|
168
|
Chris@0
|
169 public function eof()
|
Chris@0
|
170 {
|
Chris@17
|
171 if (!isset($this->stream)) {
|
Chris@17
|
172 throw new \RuntimeException('Stream is detached');
|
Chris@17
|
173 }
|
Chris@17
|
174
|
Chris@17
|
175 return feof($this->stream);
|
Chris@0
|
176 }
|
Chris@0
|
177
|
Chris@0
|
178 public function tell()
|
Chris@0
|
179 {
|
Chris@17
|
180 if (!isset($this->stream)) {
|
Chris@17
|
181 throw new \RuntimeException('Stream is detached');
|
Chris@17
|
182 }
|
Chris@17
|
183
|
Chris@0
|
184 $result = ftell($this->stream);
|
Chris@0
|
185
|
Chris@0
|
186 if ($result === false) {
|
Chris@0
|
187 throw new \RuntimeException('Unable to determine stream position');
|
Chris@0
|
188 }
|
Chris@0
|
189
|
Chris@0
|
190 return $result;
|
Chris@0
|
191 }
|
Chris@0
|
192
|
Chris@0
|
193 public function rewind()
|
Chris@0
|
194 {
|
Chris@0
|
195 $this->seek(0);
|
Chris@0
|
196 }
|
Chris@0
|
197
|
Chris@0
|
198 public function seek($offset, $whence = SEEK_SET)
|
Chris@0
|
199 {
|
Chris@17
|
200 if (!isset($this->stream)) {
|
Chris@17
|
201 throw new \RuntimeException('Stream is detached');
|
Chris@17
|
202 }
|
Chris@0
|
203 if (!$this->seekable) {
|
Chris@0
|
204 throw new \RuntimeException('Stream is not seekable');
|
Chris@17
|
205 }
|
Chris@17
|
206 if (fseek($this->stream, $offset, $whence) === -1) {
|
Chris@0
|
207 throw new \RuntimeException('Unable to seek to stream position '
|
Chris@0
|
208 . $offset . ' with whence ' . var_export($whence, true));
|
Chris@0
|
209 }
|
Chris@0
|
210 }
|
Chris@0
|
211
|
Chris@0
|
212 public function read($length)
|
Chris@0
|
213 {
|
Chris@17
|
214 if (!isset($this->stream)) {
|
Chris@17
|
215 throw new \RuntimeException('Stream is detached');
|
Chris@17
|
216 }
|
Chris@0
|
217 if (!$this->readable) {
|
Chris@0
|
218 throw new \RuntimeException('Cannot read from non-readable stream');
|
Chris@0
|
219 }
|
Chris@0
|
220 if ($length < 0) {
|
Chris@0
|
221 throw new \RuntimeException('Length parameter cannot be negative');
|
Chris@0
|
222 }
|
Chris@0
|
223
|
Chris@0
|
224 if (0 === $length) {
|
Chris@0
|
225 return '';
|
Chris@0
|
226 }
|
Chris@0
|
227
|
Chris@0
|
228 $string = fread($this->stream, $length);
|
Chris@0
|
229 if (false === $string) {
|
Chris@0
|
230 throw new \RuntimeException('Unable to read from stream');
|
Chris@0
|
231 }
|
Chris@0
|
232
|
Chris@0
|
233 return $string;
|
Chris@0
|
234 }
|
Chris@0
|
235
|
Chris@0
|
236 public function write($string)
|
Chris@0
|
237 {
|
Chris@17
|
238 if (!isset($this->stream)) {
|
Chris@17
|
239 throw new \RuntimeException('Stream is detached');
|
Chris@17
|
240 }
|
Chris@0
|
241 if (!$this->writable) {
|
Chris@0
|
242 throw new \RuntimeException('Cannot write to a non-writable stream');
|
Chris@0
|
243 }
|
Chris@0
|
244
|
Chris@0
|
245 // We can't know the size after writing anything
|
Chris@0
|
246 $this->size = null;
|
Chris@0
|
247 $result = fwrite($this->stream, $string);
|
Chris@0
|
248
|
Chris@0
|
249 if ($result === false) {
|
Chris@0
|
250 throw new \RuntimeException('Unable to write to stream');
|
Chris@0
|
251 }
|
Chris@0
|
252
|
Chris@0
|
253 return $result;
|
Chris@0
|
254 }
|
Chris@0
|
255
|
Chris@0
|
256 public function getMetadata($key = null)
|
Chris@0
|
257 {
|
Chris@0
|
258 if (!isset($this->stream)) {
|
Chris@0
|
259 return $key ? null : [];
|
Chris@0
|
260 } elseif (!$key) {
|
Chris@0
|
261 return $this->customMetadata + stream_get_meta_data($this->stream);
|
Chris@0
|
262 } elseif (isset($this->customMetadata[$key])) {
|
Chris@0
|
263 return $this->customMetadata[$key];
|
Chris@0
|
264 }
|
Chris@0
|
265
|
Chris@0
|
266 $meta = stream_get_meta_data($this->stream);
|
Chris@0
|
267
|
Chris@0
|
268 return isset($meta[$key]) ? $meta[$key] : null;
|
Chris@0
|
269 }
|
Chris@0
|
270 }
|