Completed
Push — master ( 71b082...d4eb58 )
by smiley
02:29
created

Stream::__destruct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * Class Stream
4
 *
5
 * @filesource   Stream.php
6
 * @created      11.08.2018
7
 * @package      chillerlan\HTTP\Psr7
8
 * @author       smiley <[email protected]>
9
 * @copyright    2018 smiley
10
 * @license      MIT
11
 */
12
13
namespace chillerlan\HTTP\Psr7;
14
15
use Exception, InvalidArgumentException, RuntimeException;
16
17
/**
18
 * @property resource $stream
19
 */
20
final class Stream extends StreamAbstract{
21
22
	/**
23
	 * @var bool
24
	 */
25
	private $seekable;
26
27
	/**
28
	 * @var bool
29
	 */
30
	private $readable;
31
32
	/**
33
	 * @var bool
34
	 */
35
	private $writable;
36
37
	/**
38
	 * @var string|null
39
	 */
40
	private $uri;
41
42
	/**
43
	 * @var int|null
44
	 */
45
	private $size;
46
47
	/**
48
	 * Stream constructor.
49
	 *
50
	 * @param resource $stream
51
	 */
52
	public function __construct($stream){
53
54
		if(!is_resource($stream)){
55
			throw new InvalidArgumentException('Stream must be a resource');
56
		}
57
58
		$this->stream = $stream;
59
60
		$meta = stream_get_meta_data($this->stream);
61
62
		$this->seekable = $meta['seekable'];
63
		$this->readable = isset($this::MODES_READ[$meta['mode']]);
64
		$this->writable = isset($this::MODES_WRITE[$meta['mode']]);
65
		$this->uri      = $meta['uri'] ?? null;
66
	}
67
68
	/**
69
	 * @inheritdoc
70
	 */
71
	public function __toString(){
72
73
		if(!is_resource($this->stream)){
74
			return '';
75
		}
76
77
		try{
78
79
			if($this->isSeekable()){
80
				$this->seek(0);
81
			}
82
83
			return (string)stream_get_contents($this->stream);
84
		}
85
		catch(Exception $e){
86
			// https://bugs.php.net/bug.php?id=53648
87
			trigger_error('StreamDecoratorTrait::__toString exception: '.$e->getMessage(), E_USER_ERROR);
88
89
			return '';
90
		}
91
92
	}
93
94
	/**
95
	 * @inheritdoc
96
	 */
97
	public function close():void{
98
99
		if(is_resource($this->stream)){
100
			fclose($this->stream);
101
		}
102
103
		$this->detach();
104
	}
105
106
	/**
107
	 * @inheritdoc
108
	 */
109
	public function detach(){
110
		$oldResource = $this->stream;
111
112
		$this->stream   = null;
113
		$this->size     = null;
114
		$this->uri      = null;
115
		$this->readable = false;
116
		$this->writable = false;
117
		$this->seekable = false;
118
119
		return $oldResource;
120
	}
121
122
	/**
123
	 * @inheritdoc
124
	 */
125
	public function getSize():?int{
126
127
		if($this->size !== null){
128
			return $this->size;
129
		}
130
131
		if(!isset($this->stream)){
132
			return null;
133
		}
134
135
		// Clear the stat cache if the stream has a URI
136
		if($this->uri){
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->uri of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
137
			clearstatcache(true, $this->uri);
138
		}
139
140
		$stats = fstat($this->stream);
141
142
		if(isset($stats['size'])){
143
			$this->size = $stats['size'];
144
145
			return $this->size;
146
		}
147
148
		return null;
149
	}
150
151
	/**
152
	 * @inheritdoc
153
	 */
154
	public function tell():int{
155
		$result = ftell($this->stream);
156
157
		if($result === false){
158
			throw new RuntimeException('Unable to determine stream position');
159
		}
160
161
		return $result;
162
	}
163
164
	/**
165
	 * @inheritdoc
166
	 */
167
	public function eof():bool{
168
		return !$this->stream || feof($this->stream);
169
	}
170
171
	/**
172
	 * @inheritdoc
173
	 */
174
	public function isSeekable():bool{
175
		return $this->seekable;
176
	}
177
178
	/**
179
	 * @inheritdoc
180
	 */
181
	public function seek($offset, $whence = SEEK_SET):void{
182
183
		if(!$this->seekable){
184
			throw new RuntimeException('Stream is not seekable');
185
		}
186
		elseif(fseek($this->stream, $offset, $whence) === -1){
187
			throw new RuntimeException('Unable to seek to stream position '.$offset.' with whence '.var_export($whence, true));
188
		}
189
190
	}
191
192
	/**
193
	 * @inheritdoc
194
	 */
195
	public function rewind():void{
196
		$this->seek(0);
197
	}
198
199
	/**
200
	 * @inheritdoc
201
	 */
202
	public function isWritable():bool{
203
		return $this->writable;
204
	}
205
206
	/**
207
	 * @inheritdoc
208
	 */
209
	public function write($string):int{
210
211
		if(!$this->writable){
212
			throw new RuntimeException('Cannot write to a non-writable stream');
213
		}
214
215
		// We can't know the size after writing anything
216
		$this->size = null;
217
		$result     = fwrite($this->stream, $string);
218
219
		if($result === false){
220
			throw new RuntimeException('Unable to write to stream');
221
		}
222
223
		return $result;
224
	}
225
226
	/**
227
	 * @inheritdoc
228
	 */
229
	public function isReadable():bool{
230
		return $this->readable;
231
	}
232
233
	/**
234
	 * @inheritdoc
235
	 */
236
	public function read($length):string{
237
238
		if(!$this->readable){
239
			throw new RuntimeException('Cannot read from non-readable stream');
240
		}
241
		if($length < 0){
242
			throw new RuntimeException('Length parameter cannot be negative');
243
		}
244
245
		if($length === 0){
246
			return '';
247
		}
248
249
		$string = fread($this->stream, $length);
250
251
		if($string === false){
252
			throw new RuntimeException('Unable to read from stream');
253
		}
254
255
		return $string;
256
	}
257
258
	/**
259
	 * @inheritdoc
260
	 */
261
	public function getContents():string{
262
		$contents = stream_get_contents($this->stream);
263
264
		if($contents === false){
265
			throw new RuntimeException('Unable to read stream contents');
266
		}
267
268
		return $contents;
269
	}
270
271
	/**
272
	 * @inheritdoc
273
	 */
274
	public function getMetadata($key = null){
275
276
		if(!isset($this->stream)){
277
			return $key ? null : [];
278
		}
279
		elseif($key === null){
280
			return stream_get_meta_data($this->stream);
281
		}
282
283
		$meta = stream_get_meta_data($this->stream);
284
285
		return isset($meta[$key]) ? $meta[$key] : null;
286
	}
287
288
}
289