1: <?php
2: 3: 4: 5: 6: 7: 8: 9:
10:
11: namespace Kotchasan\Http;
12:
13: use Psr\Http\Message\StreamInterface;
14:
15: 16: 17: 18: 19: 20: 21:
22: class Stream implements StreamInterface
23: {
24: 25: 26: 27: 28:
29: protected $meta;
30: 31: 32: 33: 34:
35: protected $readable;
36: 37: 38: 39: 40:
41: protected $seekable;
42: 43: 44: 45: 46:
47: protected $size;
48: 49: 50: 51: 52:
53: protected $stream;
54: 55: 56: 57: 58:
59: protected $writable;
60:
61: 62: 63: 64: 65: 66: 67: 68:
69: public function __construct($stream, $mode = 'r')
70: {
71: if (is_string($stream)) {
72: set_error_handler(function ($e) use (&$error) {
73: $error = $e;
74: }, E_WARNING);
75: $stream = fopen($stream, $mode);
76: restore_error_handler();
77: }
78: if (!is_resource($stream)) {
79: throw new \InvalidArgumentException('Stream must be a resource');
80: }
81: $this->stream = $stream;
82: }
83:
84: 85: 86: 87: 88:
89: public function __toString()
90: {
91: if (is_resource($this->stream)) {
92: try {
93: $this->rewind();
94:
95: return $this->getContents();
96: } catch (\RuntimeException $e) {
97: }
98: }
99:
100: return '';
101: }
102:
103: 104: 105:
106: public function close()
107: {
108: if (isset($this->stream)) {
109: if (is_resource($this->stream)) {
110: fclose($this->stream);
111: }
112: $this->detach();
113: }
114: }
115:
116: 117: 118: 119: 120: 121:
122: public function detach()
123: {
124: $tmp = $this->stream;
125: $this->meta = null;
126: $this->readable = null;
127: $this->seekable = null;
128: $this->size = null;
129: $this->stream = null;
130: $this->writable = null;
131:
132: return $tmp;
133: }
134:
135: 136: 137: 138: 139: 140:
141: public function eof()
142: {
143: return is_resource($this->stream) ? feof($this->stream) : true;
144: }
145:
146: 147: 148: 149: 150: 151: 152:
153: public function getContents()
154: {
155: $contents = stream_get_contents($this->stream);
156: if ($contents === false) {
157: throw new \RuntimeException('Unable to read stream contents');
158: }
159:
160: return $contents;
161: }
162:
163: 164: 165: 166: 167: 168: 169: 170: 171: 172:
173: public function getMetadata($key = null)
174: {
175: if ($this->meta === null) {
176: $this->meta = is_resource($this->stream) ? stream_get_meta_data($this->stream) : null;
177: }
178: if ($key === null) {
179: return $this->meta;
180: } else {
181: return isset($this->meta[$key]) ? $this->meta[$key] : null;
182: }
183: }
184:
185: 186: 187: 188: 189: 190:
191: public function getSize()
192: {
193: if ($this->size === null) {
194: if (is_resource($this->stream)) {
195: $stats = fstat($this->stream);
196: $this->size = isset($stats['size']) ? $stats['size'] : null;
197: } else {
198: $this->size = null;
199: }
200: }
201:
202: return $this->size;
203: }
204:
205: 206: 207: 208: 209: 210:
211: public function isReadable()
212: {
213: if ($this->readable === null) {
214: $mode = $this->getMetadata('mode');
215: $this->readable = $mode === null ? false : (strstr($mode, 'r') || strstr($mode, '+'));
216: }
217:
218: return $this->readable;
219: }
220:
221: 222: 223: 224: 225: 226:
227: public function isSeekable()
228: {
229: if ($this->seekable === null) {
230: $this->seekable = $this->getMetadata('seekable');
231: }
232:
233: return $this->seekable;
234: }
235:
236: 237: 238: 239: 240: 241:
242: public function isWritable()
243: {
244: if ($this->writable === null) {
245: $mode = $this->getMetadata('mode');
246: $this->writable = $mode === null ? false : (strstr($mode, 'x') || strstr($mode, 'w') || strstr($mode, 'c') || strstr($mode, 'a') || strstr($mode, '+'));
247: }
248:
249: return $this->writable;
250: }
251:
252: 253: 254: 255: 256: 257: 258: 259: 260:
261: public function read($length)
262: {
263: $data = is_resource($this->stream) ? fread($this->stream, $length) : false;
264: if ($data === false) {
265: throw new \RuntimeException('Unable to read stream contents');
266: }
267:
268: return $data;
269: }
270:
271: 272: 273: 274: 275:
276: public function rewind()
277: {
278: return $this->seek(0);
279: }
280:
281: 282: 283: 284: 285: 286: 287: 288:
289: public function seek($offset, $whence = SEEK_SET)
290: {
291: if (fseek($this->stream, $offset, $whence) === -1) {
292: throw new \RuntimeException('Error seeking within stream');
293: }
294: }
295:
296: 297: 298: 299: 300: 301: 302:
303: public function tell()
304: {
305: $position = is_resource($this->stream) ? ftell($this->stream) : false;
306: if ($position === false) {
307: throw new \RuntimeException('Unable to determine stream position');
308: }
309:
310: return $position;
311: }
312:
313: 314: 315: 316: 317: 318: 319: 320: 321: 322:
323: public function write($string)
324: {
325: $result = is_resource($this->stream) ? fwrite($this->stream, $string) : false;
326: if ($result === false) {
327: throw new \RuntimeException('Unable to write to stream');
328: } else {
329: $this->size = null;
330: }
331:
332: return $result;
333: }
334: }
335: