1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace CodeJet\Http; |
4
|
|
|
|
5
|
|
|
use InvalidArgumentException; |
6
|
|
|
use Psr\Http\Message\StreamInterface; |
7
|
|
|
|
8
|
|
|
class Stream implements StreamInterface |
9
|
|
|
{ |
10
|
|
|
|
11
|
|
|
protected $handle; |
12
|
|
|
|
13
|
|
|
/** |
14
|
|
|
* Stream constructor. |
15
|
|
|
* @param $stream |
16
|
|
|
*/ |
17
|
|
|
public function __construct($stream) |
18
|
|
|
{ |
19
|
|
|
if (!$this->isStream($stream)) { |
20
|
|
|
throw new InvalidArgumentException('Must be a stream.'); |
21
|
|
|
} |
22
|
|
|
|
23
|
|
|
$this->handle = $stream; |
24
|
|
|
} |
25
|
|
|
|
26
|
|
|
/** |
27
|
|
|
* @param $stream |
28
|
|
|
* @return bool |
29
|
|
|
*/ |
30
|
|
|
protected function isStream($stream) |
31
|
|
|
{ |
32
|
|
|
if (is_resource($stream) && get_resource_type($stream) === 'stream') { |
33
|
|
|
return true; |
34
|
|
|
} |
35
|
|
|
|
36
|
|
|
return false; |
37
|
|
|
} |
38
|
|
|
|
39
|
|
|
/** |
40
|
|
|
* @inheritdoc |
41
|
|
|
*/ |
42
|
|
|
public function __toString() |
43
|
|
|
{ |
44
|
|
|
if (!$this->isReadable()) { |
45
|
|
|
return ''; |
46
|
|
|
} |
47
|
|
|
|
48
|
|
|
if ($this->isSeekable()) { |
49
|
|
|
$this->rewind(); |
50
|
|
|
} |
51
|
|
|
|
52
|
|
|
return stream_get_contents($this->handle); |
53
|
|
|
} |
54
|
|
|
|
55
|
|
|
/** |
56
|
|
|
* @inheritdoc |
57
|
|
|
*/ |
58
|
|
|
public function close() |
59
|
|
|
{ |
60
|
|
|
if (!$this->handle) { |
61
|
|
|
return; |
62
|
|
|
} |
63
|
|
|
|
64
|
|
|
$handle = $this->detach(); |
65
|
|
|
fclose($handle); |
66
|
|
|
} |
67
|
|
|
|
68
|
|
|
/** |
69
|
|
|
* @inheritdoc |
70
|
|
|
*/ |
71
|
|
|
public function detach() |
72
|
|
|
{ |
73
|
|
|
$handle = $this->handle; |
74
|
|
|
$this->handle = null; |
75
|
|
|
|
76
|
|
|
return $handle; |
77
|
|
|
} |
78
|
|
|
|
79
|
|
|
/** |
80
|
|
|
* @inheritdoc |
81
|
|
|
*/ |
82
|
|
|
public function getSize() |
83
|
|
|
{ |
84
|
|
|
$fstatData = fstat($this->handle); |
85
|
|
|
|
86
|
|
|
if (isset($fstatData['size'])) { |
87
|
|
|
return $fstatData['size']; |
88
|
|
|
} |
89
|
|
|
|
90
|
|
|
return null; |
91
|
|
|
} |
92
|
|
|
|
93
|
|
|
/** |
94
|
|
|
* @inheritdoc |
95
|
|
|
*/ |
96
|
|
|
public function tell() |
97
|
|
|
{ |
98
|
|
|
if (!$this->handle) { |
99
|
|
|
throw new \RuntimeException('Cannot tell position, stream is closed.'); |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
return ftell($this->handle); |
103
|
|
|
} |
104
|
|
|
|
105
|
|
|
/** |
106
|
|
|
* @inheritdoc |
107
|
|
|
*/ |
108
|
|
|
public function eof() |
109
|
|
|
{ |
110
|
|
|
if (!$this->handle) { |
111
|
|
|
return true; |
112
|
|
|
} |
113
|
|
|
|
114
|
|
|
return feof($this->handle); |
115
|
|
|
} |
116
|
|
|
|
117
|
|
|
/** |
118
|
|
|
* @inheritdoc |
119
|
|
|
*/ |
120
|
|
|
public function isSeekable() |
121
|
|
|
{ |
122
|
|
|
if (!$this->handle) { |
123
|
|
|
return false; |
124
|
|
|
} |
125
|
|
|
|
126
|
|
|
if ($this->getMetadata('seekable')) { |
127
|
|
|
return true; |
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
return false; |
131
|
|
|
} |
132
|
|
|
|
133
|
|
|
/** |
134
|
|
|
* @inheritdoc |
135
|
|
|
*/ |
136
|
|
|
public function seek($offset, $whence = SEEK_SET) |
137
|
|
|
{ |
138
|
|
|
if (!$this->isSeekable()) { |
139
|
|
|
throw new \RuntimeException('Stream is not seekable.'); |
140
|
|
|
} |
141
|
|
|
|
142
|
|
|
fseek($this->handle, $offset, $whence); |
143
|
|
|
} |
144
|
|
|
|
145
|
|
|
/** |
146
|
|
|
* @inheritdoc |
147
|
|
|
*/ |
148
|
|
|
public function rewind() |
149
|
|
|
{ |
150
|
|
|
if (!$this->isSeekable()) { |
151
|
|
|
throw new \RuntimeException('Cannot rewind, stream is not seekable.'); |
152
|
|
|
} |
153
|
|
|
|
154
|
|
|
$this->seek(0); |
155
|
|
|
} |
156
|
|
|
|
157
|
|
|
/** |
158
|
|
|
* @inheritdoc |
159
|
|
|
*/ |
160
|
|
|
public function isWritable() |
161
|
|
|
{ |
162
|
|
|
if (!$this->handle) { |
163
|
|
|
return false; |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
$mode = $this->getMetadata('mode'); |
167
|
|
|
|
168
|
|
|
if (!$mode) { |
169
|
|
|
return false; |
170
|
|
|
} |
171
|
|
|
|
172
|
|
|
// Nix 'b'inary and 't'ext only identifiers from the mode. |
173
|
|
|
$mode = rtrim(rtrim($mode, 'b'), 't'); |
174
|
|
|
|
175
|
|
|
if ($mode === 'r') { |
176
|
|
|
// 'r' is the only read-only mode. |
177
|
|
|
// The rest are read-write or write-only. |
178
|
|
|
return false; |
179
|
|
|
} |
180
|
|
|
|
181
|
|
|
return true; |
182
|
|
|
} |
183
|
|
|
|
184
|
|
|
/** |
185
|
|
|
* @inheritdoc |
186
|
|
|
*/ |
187
|
|
|
public function write($string) |
188
|
|
|
{ |
189
|
|
|
if (!$this->isWritable($this->handle)) { |
|
|
|
|
190
|
|
|
throw new \RuntimeException('Stream is not writable.'); |
191
|
|
|
} |
192
|
|
|
|
193
|
|
|
if (!is_string($string)) { |
194
|
|
|
throw new \RuntimeException('Stream write value must be a string.'); |
195
|
|
|
} |
196
|
|
|
|
197
|
|
|
return fwrite($this->handle, $string); |
198
|
|
|
} |
199
|
|
|
|
200
|
|
|
/** |
201
|
|
|
* @inheritdoc |
202
|
|
|
*/ |
203
|
|
|
public function isReadable() |
204
|
|
|
{ |
205
|
|
|
if (!$this->handle) { |
206
|
|
|
return false; |
207
|
|
|
} |
208
|
|
|
|
209
|
|
|
$mode = $this->getMetadata('mode'); |
210
|
|
|
|
211
|
|
|
if (!$mode) { |
212
|
|
|
return false; |
213
|
|
|
} |
214
|
|
|
|
215
|
|
|
// Nix 'b'inary and 't'ext only identifiers from the mode. |
216
|
|
|
$mode = rtrim(rtrim($mode, 'b'), 't'); |
217
|
|
|
// Grab the last character of the mode. |
218
|
|
|
$readIdentifier = substr($mode, -1, 1); |
219
|
|
|
|
220
|
|
|
if ($readIdentifier === 'r' || $readIdentifier === '+') { |
221
|
|
|
return true; |
222
|
|
|
} |
223
|
|
|
|
224
|
|
|
return false; |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
/** |
228
|
|
|
* @inheritdoc |
229
|
|
|
*/ |
230
|
|
|
public function read($length) |
231
|
|
|
{ |
232
|
|
|
if (!$this->isReadable()) { |
233
|
|
|
throw new \RuntimeException('Stream is not readable.'); |
234
|
|
|
} |
235
|
|
|
|
236
|
|
|
$data = fread($this->handle, $length); |
237
|
|
|
|
238
|
|
|
return $data; |
239
|
|
|
} |
240
|
|
|
|
241
|
|
|
/** |
242
|
|
|
* @inheritdoc |
243
|
|
|
*/ |
244
|
|
|
public function getContents() |
245
|
|
|
{ |
246
|
|
|
if (!$this->isReadable()) { |
247
|
|
|
throw new \RuntimeException('Stream is not readable.'); |
248
|
|
|
} |
249
|
|
|
|
250
|
|
|
return stream_get_contents($this->handle); |
251
|
|
|
} |
252
|
|
|
|
253
|
|
|
/** |
254
|
|
|
* @inheritdoc |
255
|
|
|
*/ |
256
|
|
|
public function getMetadata($key = null) |
257
|
|
|
{ |
258
|
|
|
$metaData = stream_get_meta_data($this->handle); |
259
|
|
|
|
260
|
|
|
if (is_null($key)) { |
261
|
|
|
return $metaData; |
262
|
|
|
} |
263
|
|
|
|
264
|
|
|
if (isset($metaData[$key])) { |
265
|
|
|
return $metaData[$key]; |
266
|
|
|
} |
267
|
|
|
|
268
|
|
|
return null; |
269
|
|
|
} |
270
|
|
|
} |
271
|
|
|
|
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.
If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.
In this case you can add the
@ignore
PhpDoc annotation to the duplicate definition and it will be ignored.