Passed
Push — main ( 5b8cd4...64b6a7 )
by smiley
01:44
created

src/Psr7/Stream.php (2 issues)

Labels
Severity
1
<?php
2
/**
3
 * Class Stream
4
 *
5
 * @created      11.08.2018
6
 * @author       smiley <[email protected]>
7
 * @copyright    2018 smiley
8
 * @license      MIT
9
 */
10
11
namespace chillerlan\HTTP\Psr7;
12
13
use InvalidArgumentException, RuntimeException;
14
15
use function clearstatcache, fclose, feof, fread, fstat, ftell, fwrite, in_array,
16
	is_resource, stream_get_contents, stream_get_meta_data;
17
18
use const SEEK_SET;
19
use const chillerlan\HTTP\Psr17\{STREAM_MODES_READ, STREAM_MODES_WRITE};
0 ignored issues
show
The type chillerlan\HTTP\Psr17\STREAM_MODES_READ was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
The type chillerlan\HTTP\Psr17\STREAM_MODES_WRITE was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
20
21
/**
22
 * @property resource|null $stream
23
 */
24
final class Stream extends StreamAbstract{
25
26
	private bool $seekable;
27
28
	private bool $readable;
29
30
	private bool $writable;
31
32
	private ?string $uri = null;
33
34
	private ?int $size = null;
35
36
	/**
37
	 * Stream constructor.
38
	 *
39
	 * @param resource $stream
40
	 */
41
	public function __construct($stream){
42
43
		if(!is_resource($stream)){
44
			throw new InvalidArgumentException('Stream must be a resource');
45
		}
46
47
		$this->stream = $stream;
48
49
		$meta = stream_get_meta_data($this->stream);
50
51
		$this->seekable = $meta['seekable'];
52
		$this->readable = in_array($meta['mode'], STREAM_MODES_READ);
53
		$this->writable = in_array($meta['mode'], STREAM_MODES_WRITE);
54
		$this->uri      = $meta['uri'] ?? null;
55
	}
56
57
	/**
58
	 * @inheritDoc
59
	 */
60
	public function __toString(){
61
62
		if(!is_resource($this->stream)){
63
			return '';
64
		}
65
66
		if($this->isSeekable()){
67
			$this->seek(0);
68
		}
69
70
		$contents = stream_get_contents($this->stream);
71
72
		if($contents !== false){
73
			return $contents;
74
		}
75
76
		throw new RuntimeException('stream_get_contents() error'); // @codeCoverageIgnore
77
	}
78
79
	/**
80
	 * @inheritDoc
81
	 */
82
	public function close():void{
83
84
		if(is_resource($this->stream)){
85
			fclose($this->stream);
86
		}
87
88
		$this->detach();
89
	}
90
91
	/**
92
	 * @inheritDoc
93
	 */
94
	public function detach(){
95
		$oldResource = $this->stream;
96
97
		$this->stream   = null;
98
		$this->size     = null;
99
		$this->uri      = null;
100
		$this->readable = false;
101
		$this->writable = false;
102
		$this->seekable = false;
103
104
		return $oldResource;
105
	}
106
107
	/**
108
	 * @inheritDoc
109
	 */
110
	public function getSize():?int{
111
112
		if($this->size !== null){
113
			return $this->size;
114
		}
115
116
		if(!is_resource($this->stream)){
117
			return null;
118
		}
119
120
		// Clear the stat cache if the stream has a URI
121
		if($this->uri){
122
			clearstatcache(true, $this->uri);
123
		}
124
125
		$stats = fstat($this->stream);
126
127
		if(isset($stats['size'])){
128
			$this->size = $stats['size'];
129
130
			return $this->size;
131
		}
132
133
		return null; // @codeCoverageIgnore
134
	}
135
136
	/**
137
	 * @inheritDoc
138
	 */
139
	public function tell():int{
140
141
		if(!is_resource($this->stream)){
142
			throw new RuntimeException('Invalid stream'); // @codeCoverageIgnore
143
		}
144
145
		$result = ftell($this->stream);
146
147
		if($result === false){
148
			throw new RuntimeException('Unable to determine stream position'); // @codeCoverageIgnore
149
		}
150
151
		return $result;
152
	}
153
154
	/**
155
	 * @inheritDoc
156
	 */
157
	public function eof():bool{
158
		return !$this->stream || feof($this->stream);
159
	}
160
161
	/**
162
	 * @inheritDoc
163
	 */
164
	public function isSeekable():bool{
165
		return $this->seekable;
166
	}
167
168
	/**
169
	 * @inheritDoc
170
	 */
171
	public function seek($offset, $whence = SEEK_SET):void{
172
173
		if(!is_resource($this->stream)){
174
			throw new RuntimeException('Invalid stream'); // @codeCoverageIgnore
175
		}
176
177
		if(!$this->seekable){
178
			throw new RuntimeException('Stream is not seekable');
179
		}
180
		elseif(fseek($this->stream, $offset, $whence) === -1){
181
			throw new RuntimeException('Unable to seek to stream position '.$offset.' with whence '.$whence);
182
		}
183
184
	}
185
186
	/**
187
	 * @inheritDoc
188
	 */
189
	public function rewind():void{
190
		$this->seek(0);
191
	}
192
193
	/**
194
	 * @inheritDoc
195
	 */
196
	public function isWritable():bool{
197
		return $this->writable;
198
	}
199
200
	/**
201
	 * @inheritDoc
202
	 */
203
	public function write($string):int{
204
205
		if(!is_resource($this->stream)){
206
			throw new RuntimeException('Invalid stream'); // @codeCoverageIgnore
207
		}
208
209
		if(!$this->writable){
210
			throw new RuntimeException('Cannot write to a non-writable stream');
211
		}
212
213
		// We can't know the size after writing anything
214
		$this->size = null;
215
		$result     = fwrite($this->stream, $string);
216
217
		if($result === false){
218
			throw new RuntimeException('Unable to write to stream'); // @codeCoverageIgnore
219
		}
220
221
		return $result;
222
	}
223
224
	/**
225
	 * @inheritDoc
226
	 */
227
	public function isReadable():bool{
228
		return $this->readable;
229
	}
230
231
	/**
232
	 * @inheritDoc
233
	 */
234
	public function read($length):string{
235
236
		if(!is_resource($this->stream)){
237
			throw new RuntimeException('Invalid stream'); // @codeCoverageIgnore
238
		}
239
240
		if(!$this->readable){
241
			throw new RuntimeException('Cannot read from non-readable stream');
242
		}
243
244
		if($length < 0){
245
			throw new RuntimeException('Length parameter cannot be negative');
246
		}
247
248
		if($length === 0){
249
			return '';
250
		}
251
252
		$string = fread($this->stream, $length);
253
254
		if($string === false){
255
			throw new RuntimeException('Unable to read from stream'); // @codeCoverageIgnore
256
		}
257
258
		return $string;
259
	}
260
261
	/**
262
	 * @inheritDoc
263
	 */
264
	public function getContents():string{
265
266
		if(!is_resource($this->stream)){
267
			throw new RuntimeException('Invalid stream'); // @codeCoverageIgnore
268
		}
269
270
		$contents = stream_get_contents($this->stream);
271
272
		if($contents === false){
273
			throw new RuntimeException('Unable to read stream contents'); // @codeCoverageIgnore
274
		}
275
276
		return $contents;
277
	}
278
279
	/**
280
	 * @inheritDoc
281
	 */
282
	public function getMetadata($key = null){
283
284
		if(!is_resource($this->stream)){
285
			return $key ? null : [];
286
		}
287
		elseif($key === null){
288
			return stream_get_meta_data($this->stream);
289
		}
290
291
		$meta = stream_get_meta_data($this->stream);
292
293
		return isset($meta[$key]) ? $meta[$key] : null;
294
	}
295
296
}
297