1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* Linna Psr7. |
5
|
|
|
* |
6
|
|
|
* @author Sebastian Rapetti <[email protected]> |
7
|
|
|
* @copyright (c) 2018, Sebastian Rapetti |
8
|
|
|
* @license http://opensource.org/licenses/MIT MIT License |
9
|
|
|
*/ |
10
|
|
|
declare(strict_types=1); |
11
|
|
|
|
12
|
|
|
namespace Linna\Http\Message; |
13
|
|
|
|
14
|
|
|
use InvalidArgumentException; |
15
|
|
|
use Psr\Http\Message\StreamInterface; |
16
|
|
|
use RuntimeException; |
17
|
|
|
|
18
|
|
|
/** |
19
|
|
|
* Psr7 Stream implementation. |
20
|
|
|
*/ |
21
|
|
|
class Stream implements StreamInterface |
22
|
|
|
{ |
23
|
|
|
/** |
24
|
|
|
* @var mixed The streem resource. |
25
|
|
|
*/ |
26
|
|
|
protected $resource; |
27
|
|
|
|
28
|
|
|
/** |
29
|
|
|
* @var bool Is stream a proces file pointer? |
30
|
|
|
*/ |
31
|
|
|
protected $isPipe; |
32
|
|
|
|
33
|
|
|
/** |
34
|
|
|
* Constructor. |
35
|
|
|
* |
36
|
|
|
* @param mixed $resource |
37
|
|
|
*/ |
38
|
|
|
public function __construct($resource) |
39
|
|
|
{ |
40
|
|
|
if (!is_resource($resource)) { |
41
|
|
|
throw new InvalidArgumentException(__CLASS__.': Invalid resource provided'); |
42
|
|
|
} |
43
|
|
|
|
44
|
|
|
if ('stream' !== get_resource_type($resource)) { |
45
|
|
|
throw new InvalidArgumentException(__CLASS__.': Resource provided is not a stream'); |
46
|
|
|
} |
47
|
|
|
|
48
|
|
|
$this->resource = $resource; |
49
|
|
|
$this->isPipe = $this->checkFileMode($resource); |
50
|
|
|
} |
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* Check if file is a pipe. |
54
|
|
|
* http://man7.org/linux/man-pages/man7/inode.7.html. |
55
|
|
|
* |
56
|
|
|
* @param mixed $resource |
57
|
|
|
* |
58
|
|
|
* @return bool |
59
|
|
|
*/ |
60
|
|
|
protected function checkFileMode($resource): bool |
61
|
|
|
{ |
62
|
|
|
//file modes |
63
|
|
|
//check if resource is a process file pointer. |
64
|
|
|
//0140000 socket |
65
|
|
|
//0120000 symbolic link |
66
|
|
|
//0100000 regular file |
67
|
|
|
//0060000 block device |
68
|
|
|
//0040000 directory |
69
|
|
|
//0020000 character device |
70
|
|
|
//0010000 FIFO |
71
|
|
|
return ((fstat($resource)['mode'] & 0010000) !== 0) ? true : false; |
72
|
|
|
} |
73
|
|
|
|
74
|
|
|
/** |
75
|
|
|
* Reads all data from the stream into a string, from the beginning to end. |
76
|
|
|
* |
77
|
|
|
* This method MUST attempt to seek to the beginning of the stream before |
78
|
|
|
* reading data and read the stream until the end is reached. |
79
|
|
|
* |
80
|
|
|
* Warning: This could attempt to load a large amount of data into memory. |
81
|
|
|
* |
82
|
|
|
* This method MUST NOT raise an exception in order to conform with PHP's |
83
|
|
|
* string casting operations. |
84
|
|
|
* |
85
|
|
|
* @see http://php.net/manual/en/language.oop5.magic.php#object.tostring |
86
|
|
|
* |
87
|
|
|
* @return string |
88
|
|
|
*/ |
89
|
|
|
public function __toString(): string |
90
|
|
|
{ |
91
|
|
|
try { |
92
|
|
|
$this->rewind(); |
93
|
|
|
|
94
|
|
|
return $this->getContents(); |
95
|
|
|
} catch (RuntimeException $e) { |
96
|
|
|
unset($e); |
97
|
|
|
return ''; |
98
|
|
|
} |
99
|
|
|
} |
100
|
|
|
|
101
|
|
|
/** |
102
|
|
|
* Closes the stream and any underlying resources. |
103
|
|
|
* |
104
|
|
|
* @return void |
105
|
|
|
*/ |
106
|
|
|
public function close() |
107
|
|
|
{ |
108
|
|
|
if (!$this->resource) { |
109
|
|
|
return; |
110
|
|
|
} |
111
|
|
|
|
112
|
|
|
if ($this->isPipe) { |
113
|
|
|
pclose($this->resource); |
114
|
|
|
$this->resource = null; |
115
|
|
|
|
116
|
|
|
return; |
117
|
|
|
} |
118
|
|
|
|
119
|
|
|
fclose($this->resource); |
120
|
|
|
$this->resource = null; |
121
|
|
|
} |
122
|
|
|
|
123
|
|
|
/** |
124
|
|
|
* Separates any underlying resources from the stream. |
125
|
|
|
* |
126
|
|
|
* After the stream has been detached, the stream is in an unusable state. |
127
|
|
|
* |
128
|
|
|
* @return resource|null Underlying PHP stream, if any. |
129
|
|
|
*/ |
130
|
|
|
public function detach() |
131
|
|
|
{ |
132
|
|
|
if (!$this->resource) { |
133
|
|
|
return; |
134
|
|
|
} |
135
|
|
|
|
136
|
|
|
$tmpResource = $this->resource; |
137
|
|
|
$this->resource = false; |
138
|
|
|
|
139
|
|
|
return $tmpResource; |
140
|
|
|
} |
141
|
|
|
|
142
|
|
|
/** |
143
|
|
|
* Get the size of the stream if known. |
144
|
|
|
* |
145
|
|
|
* @return int Returns the size in bytes if known, or zero if unknown. |
146
|
|
|
*/ |
147
|
|
|
public function getSize(): int |
148
|
|
|
{ |
149
|
|
|
return (!$this->resource) ? 0 : fstat($this->resource)['size']; |
150
|
|
|
} |
151
|
|
|
|
152
|
|
|
/** |
153
|
|
|
* Returns the current position of the file read/write pointer |
154
|
|
|
* |
155
|
|
|
* @return int Position of the file pointer. |
156
|
|
|
* |
157
|
|
|
* @throws RuntimeException on error. |
158
|
|
|
*/ |
159
|
|
|
public function tell(): int |
160
|
|
|
{ |
161
|
|
|
if (!$this->resource) { |
162
|
|
|
throw new RuntimeException(__CLASS__.': No resource available; cannot tell position'); |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
if (($position = ftell($this->resource)) === false) { |
166
|
|
|
throw new RuntimeException(__CLASS__.': Error occurred during tell operation'); |
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
return $position; |
170
|
|
|
} |
171
|
|
|
|
172
|
|
|
/** |
173
|
|
|
* Returns true if the stream is at the end of the stream. |
174
|
|
|
* |
175
|
|
|
* @return bool |
176
|
|
|
*/ |
177
|
|
|
public function eof(): bool |
178
|
|
|
{ |
179
|
|
|
return (!$this->resource) ? feof($this->resource) : true; |
180
|
|
|
} |
181
|
|
|
|
182
|
|
|
/** |
183
|
|
|
* Returns whether or not the stream is seekable. |
184
|
|
|
* |
185
|
|
|
* @return bool |
186
|
|
|
*/ |
187
|
|
|
public function isSeekable(): bool |
188
|
|
|
{ |
189
|
|
|
return (!$this->resource) ? false : stream_get_meta_data($this->resource)['seekable']; |
190
|
|
|
} |
191
|
|
|
|
192
|
|
|
/** |
193
|
|
|
* Seek to a position in the stream. |
194
|
|
|
* |
195
|
|
|
* @link http://www.php.net/manual/en/function.fseek.php |
196
|
|
|
* |
197
|
|
|
* @param int $offset Stream offset |
198
|
|
|
* @param int $whence Specifies how the cursor position will be calculated based on the seek offset. |
199
|
|
|
* Valid values are identical to the built-in PHP $whence values for `fseek()`. |
200
|
|
|
* SEEK_SET: Set position equal to offset bytes. |
201
|
|
|
* SEEK_CUR: Set position to current location plus offset |
202
|
|
|
* SEEK_END: Set position to end-of-stream plus offset. |
203
|
|
|
* |
204
|
|
|
* @throws RuntimeException on failure. |
205
|
|
|
*/ |
206
|
|
|
public function seek(int $offset, int $whence = SEEK_SET) |
207
|
|
|
{ |
208
|
|
|
if (!$this->isSeekable()) { |
209
|
|
|
throw new RuntimeException(__CLASS__.': Can not seek the stream'); |
210
|
|
|
} |
211
|
|
|
|
212
|
|
|
if (fseek($this->resource, $offset, $whence) !== 0) { |
213
|
|
|
throw new RuntimeException(__CLASS__.': Error seeking within stream'); |
214
|
|
|
} |
215
|
|
|
|
216
|
|
|
return true; |
217
|
|
|
} |
218
|
|
|
|
219
|
|
|
/** |
220
|
|
|
* Seek to the beginning of the stream. |
221
|
|
|
* |
222
|
|
|
* If the stream is not seekable, this method will raise an exception; |
223
|
|
|
* otherwise, it will perform a seek(0). |
224
|
|
|
* |
225
|
|
|
* @see seek() |
226
|
|
|
* |
227
|
|
|
* @link http://www.php.net/manual/en/function.fseek.php |
228
|
|
|
* |
229
|
|
|
* @throws RuntimeException on failure. |
230
|
|
|
*/ |
231
|
|
|
public function rewind() |
232
|
|
|
{ |
233
|
|
|
if (!$this->isSeekable() || rewind($this->resource) === false) { |
234
|
|
|
throw new RuntimeException(__CLASS__.': Can not rewind the stream'); |
235
|
|
|
} |
236
|
|
|
} |
237
|
|
|
|
238
|
|
|
/** |
239
|
|
|
* Check modes. |
240
|
|
|
* |
241
|
|
|
* @param array $modes |
242
|
|
|
* |
243
|
|
|
* @return bool |
244
|
|
|
*/ |
245
|
|
|
protected function can(array $modes): bool |
246
|
|
|
{ |
247
|
|
|
$metaMode = stream_get_meta_data($this->resource)['mode']; |
248
|
|
|
|
249
|
|
|
foreach ($modes as $mode) { |
250
|
|
|
if (strpos($metaMode, $mode) !== false) { |
251
|
|
|
return true; |
252
|
|
|
} |
253
|
|
|
} |
254
|
|
|
|
255
|
|
|
return false; |
256
|
|
|
} |
257
|
|
|
|
258
|
|
|
/** |
259
|
|
|
* Returns whether or not the stream is writable. |
260
|
|
|
* |
261
|
|
|
* @return bool |
262
|
|
|
*/ |
263
|
|
|
public function isWritable(): bool |
264
|
|
|
{ |
265
|
|
|
return (!$this->resource) ? false : $this->can(['r+', 'w', 'w+', 'a', 'a+', 'x', 'x+', 'c', 'c+']); |
266
|
|
|
} |
267
|
|
|
|
268
|
|
|
/** |
269
|
|
|
* Write data to the stream. |
270
|
|
|
* |
271
|
|
|
* @param string $string The string that is to be written. |
272
|
|
|
* |
273
|
|
|
* @return int Returns the number of bytes written to the stream. |
274
|
|
|
* |
275
|
|
|
* @throws RuntimeException on failure. |
276
|
|
|
*/ |
277
|
|
|
public function write(string $string): int |
278
|
|
|
{ |
279
|
|
|
//if (!$this->resource) { |
|
|
|
|
280
|
|
|
// throw new RuntimeException(__CLASS__.': Resource not available; '.__METHOD__); |
|
|
|
|
281
|
|
|
//} |
282
|
|
|
|
283
|
|
|
if (!$this->isWritable()) { |
284
|
|
|
throw new RuntimeException(__CLASS__.': Stream is not writable; '.__METHOD__); |
285
|
|
|
} |
286
|
|
|
|
287
|
|
|
if (($bytes = fwrite($this->resource, $string)) === false) { |
288
|
|
|
throw new RuntimeException(__CLASS__.': Error writing stream; '.__METHOD__); |
289
|
|
|
} |
290
|
|
|
|
291
|
|
|
return $bytes; |
292
|
|
|
} |
293
|
|
|
|
294
|
|
|
/** |
295
|
|
|
* Returns whether or not the stream is readable. |
296
|
|
|
* |
297
|
|
|
* @return bool |
298
|
|
|
*/ |
299
|
|
|
public function isReadable(): bool |
300
|
|
|
{ |
301
|
|
|
return (!$this->resource) ? false : $this->can(['r', 'r+', 'w+', 'a+', 'x+', 'c+']); |
302
|
|
|
} |
303
|
|
|
|
304
|
|
|
/** |
305
|
|
|
* Read data from the stream. |
306
|
|
|
* |
307
|
|
|
* @param int $length Read up to $length bytes from the object and return them. |
308
|
|
|
* Fewer than $length bytes may be returned if underlying stream |
309
|
|
|
* call returns fewer bytes. |
310
|
|
|
* |
311
|
|
|
* @return string Returns the data read from the stream, or an empty string |
312
|
|
|
* if no bytes are available. |
313
|
|
|
* |
314
|
|
|
* @throws RuntimeException if an error occurs. |
315
|
|
|
*/ |
316
|
|
|
public function read(int $length): string |
317
|
|
|
{ |
318
|
|
|
//if (!$this->resource) { |
|
|
|
|
319
|
|
|
// throw new RuntimeException(__CLASS__.': Resource not available; '.__METHOD__); |
|
|
|
|
320
|
|
|
//} |
321
|
|
|
|
322
|
|
|
if (!$this->isReadable()) { |
323
|
|
|
throw new RuntimeException(__CLASS__.': Stream is not readable; '.__METHOD__); |
324
|
|
|
} |
325
|
|
|
|
326
|
|
|
if (($data = fread($this->resource, $length)) === false) { |
327
|
|
|
throw new RuntimeException(__CLASS__.': Error reading stream; '.__METHOD__); |
328
|
|
|
} |
329
|
|
|
|
330
|
|
|
return $data; |
331
|
|
|
} |
332
|
|
|
|
333
|
|
|
/** |
334
|
|
|
* Returns the remaining contents in a string. |
335
|
|
|
* |
336
|
|
|
* @return string |
337
|
|
|
* |
338
|
|
|
* @throws RuntimeException if unable to read or an error occurs while |
339
|
|
|
* reading. |
340
|
|
|
*/ |
341
|
|
|
public function getContents(): string |
342
|
|
|
{ |
343
|
|
|
if (!$this->isReadable()) { |
344
|
|
|
throw new RuntimeException(__CLASS__.': Stream is not readable; '.__METHOD__); |
345
|
|
|
} |
346
|
|
|
|
347
|
|
|
if (($content = stream_get_contents($this->resource)) === false) { |
348
|
|
|
throw new RuntimeException(__CLASS__.': Error reading stream; '.__METHOD__); |
349
|
|
|
} |
350
|
|
|
|
351
|
|
|
return $content; |
352
|
|
|
} |
353
|
|
|
|
354
|
|
|
/** |
355
|
|
|
* Get stream metadata as an associative array or retrieve a specific key. |
356
|
|
|
* |
357
|
|
|
* The keys returned are identical to the keys returned from PHP's |
358
|
|
|
* stream_get_meta_data() function. |
359
|
|
|
* |
360
|
|
|
* @link http://php.net/manual/en/function.stream-get-meta-data.php |
361
|
|
|
* |
362
|
|
|
* @param string $key Specific metadata to retrieve. |
363
|
|
|
* |
364
|
|
|
* @return array Returns an associative array if no key is provided. |
365
|
|
|
* Returns an associative array with the specific key value |
366
|
|
|
* if a key is provided and the value is found, or a void |
367
|
|
|
* array if the key is not found. |
368
|
|
|
*/ |
369
|
|
|
public function getMetadata(string $key = ''): array |
370
|
|
|
{ |
371
|
|
|
$metadata = stream_get_meta_data($this->resource); |
372
|
|
|
|
373
|
|
|
//if key is empty strung |
374
|
|
|
return ($key === '') ? |
375
|
|
|
//return metadata |
376
|
|
|
$metadata : |
377
|
|
|
//else check if key exist and if key exist return as array else return void array |
378
|
|
|
(isset($metadata[$key]) ? [$key => $metadata[$key]] : []); |
379
|
|
|
} |
380
|
|
|
} |
381
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.