20ee0577a132701bc9ab4f3229398026b6d553fb
[yaffs-website] / src / Stream.php
1 <?php
2 /**
3  * @see       https://github.com/zendframework/zend-diactoros for the canonical source repository
4  * @copyright Copyright (c) 2015-2017 Zend Technologies USA Inc. (http://www.zend.com)
5  * @license   https://github.com/zendframework/zend-diactoros/blob/master/LICENSE.md New BSD License
6  */
7
8 namespace Zend\Diactoros;
9
10 use InvalidArgumentException;
11 use Psr\Http\Message\StreamInterface;
12 use RuntimeException;
13
14 use function array_key_exists;
15 use function fclose;
16 use function feof;
17 use function fopen;
18 use function fread;
19 use function fseek;
20 use function fstat;
21 use function ftell;
22 use function fwrite;
23 use function get_resource_type;
24 use function is_int;
25 use function is_resource;
26 use function is_string;
27 use function restore_error_handler;
28 use function set_error_handler;
29 use function stream_get_contents;
30 use function stream_get_meta_data;
31 use function strstr;
32
33 use const E_WARNING;
34 use const SEEK_SET;
35
36 /**
37  * Implementation of PSR HTTP streams
38  */
39 class Stream implements StreamInterface
40 {
41     /**
42      * @var resource|null
43      */
44     protected $resource;
45
46     /**
47      * @var string|resource
48      */
49     protected $stream;
50
51     /**
52      * @param string|resource $stream
53      * @param string $mode Mode with which to open stream
54      * @throws InvalidArgumentException
55      */
56     public function __construct($stream, $mode = 'r')
57     {
58         $this->setStream($stream, $mode);
59     }
60
61     /**
62      * {@inheritdoc}
63      */
64     public function __toString()
65     {
66         if (! $this->isReadable()) {
67             return '';
68         }
69
70         try {
71             if ($this->isSeekable()) {
72                 $this->rewind();
73             }
74
75             return $this->getContents();
76         } catch (RuntimeException $e) {
77             return '';
78         }
79     }
80
81     /**
82      * {@inheritdoc}
83      */
84     public function close()
85     {
86         if (! $this->resource) {
87             return;
88         }
89
90         $resource = $this->detach();
91         fclose($resource);
92     }
93
94     /**
95      * {@inheritdoc}
96      */
97     public function detach()
98     {
99         $resource = $this->resource;
100         $this->resource = null;
101         return $resource;
102     }
103
104     /**
105      * Attach a new stream/resource to the instance.
106      *
107      * @param string|resource $resource
108      * @param string $mode
109      * @throws InvalidArgumentException for stream identifier that cannot be
110      *     cast to a resource
111      * @throws InvalidArgumentException for non-resource stream
112      */
113     public function attach($resource, $mode = 'r')
114     {
115         $this->setStream($resource, $mode);
116     }
117
118     /**
119      * {@inheritdoc}
120      */
121     public function getSize()
122     {
123         if (null === $this->resource) {
124             return null;
125         }
126
127         $stats = fstat($this->resource);
128         if ($stats !== false) {
129             return $stats['size'];
130         }
131
132         return null;
133     }
134
135     /**
136      * {@inheritdoc}
137      */
138     public function tell()
139     {
140         if (! $this->resource) {
141             throw new RuntimeException('No resource available; cannot tell position');
142         }
143
144         $result = ftell($this->resource);
145         if (! is_int($result)) {
146             throw new RuntimeException('Error occurred during tell operation');
147         }
148
149         return $result;
150     }
151
152     /**
153      * {@inheritdoc}
154      */
155     public function eof()
156     {
157         if (! $this->resource) {
158             return true;
159         }
160
161         return feof($this->resource);
162     }
163
164     /**
165      * {@inheritdoc}
166      */
167     public function isSeekable()
168     {
169         if (! $this->resource) {
170             return false;
171         }
172
173         $meta = stream_get_meta_data($this->resource);
174         return $meta['seekable'];
175     }
176
177     /**
178      * {@inheritdoc}
179      */
180     public function seek($offset, $whence = SEEK_SET)
181     {
182         if (! $this->resource) {
183             throw new RuntimeException('No resource available; cannot seek position');
184         }
185
186         if (! $this->isSeekable()) {
187             throw new RuntimeException('Stream is not seekable');
188         }
189
190         $result = fseek($this->resource, $offset, $whence);
191
192         if (0 !== $result) {
193             throw new RuntimeException('Error seeking within stream');
194         }
195
196         return true;
197     }
198
199     /**
200      * {@inheritdoc}
201      */
202     public function rewind()
203     {
204         return $this->seek(0);
205     }
206
207     /**
208      * {@inheritdoc}
209      */
210     public function isWritable()
211     {
212         if (! $this->resource) {
213             return false;
214         }
215
216         $meta = stream_get_meta_data($this->resource);
217         $mode = $meta['mode'];
218
219         return (
220             strstr($mode, 'x')
221             || strstr($mode, 'w')
222             || strstr($mode, 'c')
223             || strstr($mode, 'a')
224             || strstr($mode, '+')
225         );
226     }
227
228     /**
229      * {@inheritdoc}
230      */
231     public function write($string)
232     {
233         if (! $this->resource) {
234             throw new RuntimeException('No resource available; cannot write');
235         }
236
237         if (! $this->isWritable()) {
238             throw new RuntimeException('Stream is not writable');
239         }
240
241         $result = fwrite($this->resource, $string);
242
243         if (false === $result) {
244             throw new RuntimeException('Error writing to stream');
245         }
246         return $result;
247     }
248
249     /**
250      * {@inheritdoc}
251      */
252     public function isReadable()
253     {
254         if (! $this->resource) {
255             return false;
256         }
257
258         $meta = stream_get_meta_data($this->resource);
259         $mode = $meta['mode'];
260
261         return (strstr($mode, 'r') || strstr($mode, '+'));
262     }
263
264     /**
265      * {@inheritdoc}
266      */
267     public function read($length)
268     {
269         if (! $this->resource) {
270             throw new RuntimeException('No resource available; cannot read');
271         }
272
273         if (! $this->isReadable()) {
274             throw new RuntimeException('Stream is not readable');
275         }
276
277         $result = fread($this->resource, $length);
278
279         if (false === $result) {
280             throw new RuntimeException('Error reading stream');
281         }
282
283         return $result;
284     }
285
286     /**
287      * {@inheritdoc}
288      */
289     public function getContents()
290     {
291         if (! $this->isReadable()) {
292             throw new RuntimeException('Stream is not readable');
293         }
294
295         $result = stream_get_contents($this->resource);
296         if (false === $result) {
297             throw new RuntimeException('Error reading from stream');
298         }
299         return $result;
300     }
301
302     /**
303      * {@inheritdoc}
304      */
305     public function getMetadata($key = null)
306     {
307         if (null === $key) {
308             return stream_get_meta_data($this->resource);
309         }
310
311         $metadata = stream_get_meta_data($this->resource);
312         if (! array_key_exists($key, $metadata)) {
313             return null;
314         }
315
316         return $metadata[$key];
317     }
318
319     /**
320      * Set the internal stream resource.
321      *
322      * @param string|resource $stream String stream target or stream resource.
323      * @param string $mode Resource mode for stream target.
324      * @throws InvalidArgumentException for invalid streams or resources.
325      */
326     private function setStream($stream, $mode = 'r')
327     {
328         $error    = null;
329         $resource = $stream;
330
331         if (is_string($stream)) {
332             set_error_handler(function ($e) use (&$error) {
333                 if ($e !== E_WARNING) {
334                     return;
335                 }
336
337                 $error = $e;
338             });
339             $resource = fopen($stream, $mode);
340             restore_error_handler();
341         }
342
343         if ($error) {
344             throw new InvalidArgumentException('Invalid stream reference provided');
345         }
346
347         if (! is_resource($resource) || 'stream' !== get_resource_type($resource)) {
348             throw new InvalidArgumentException(
349                 'Invalid stream provided; must be a string stream identifier or stream resource'
350             );
351         }
352
353         if ($stream !== $resource) {
354             $this->stream = $stream;
355         }
356
357         $this->resource = $resource;
358     }
359 }