Completed
Push — master ( 988ea9...868c80 )
by smiley
07:26
created

Stream   A

Complexity

Total Complexity 40

Size/Duplication

Total Lines 273
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 0
Metric Value
dl 0
loc 273
rs 9.2
c 0
b 0
f 0
wmc 40
lcom 1
cbo 1

How to fix   Complexity   

Complex Class

Complex classes like Stream often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Stream, and based on these observations, apply Extract Interface, too.

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
use function clearstatcache, fclose, feof, fread, fstat, ftell, fwrite, is_resource, stream_get_contents, stream_get_meta_data;
18
19
use const SEEK_SET;
20
use const chillerlan\HTTP\Psr17\{STREAM_MODES_READ, STREAM_MODES_WRITE};
21
22
/**
23
 * @property resource|null $stream
24
 */
25
final class Stream extends StreamAbstract{
26
27
	private bool $seekable;
0 ignored issues
show
Bug introduced by
This code did not parse for me. Apparently, there is an error somewhere around this line:

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