Passed
Push — main ( f2efe3...53dc0a )
by smiley
10:15
created

Stream   B

Complexity

Total Complexity 45

Size/Duplication

Total Lines 273
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 94
dl 0
loc 273
rs 8.8
c 1
b 0
f 0
wmc 45

16 Methods

Rating   Name   Duplication   Size   Complexity  
A close() 0 7 2
A __toString() 0 17 4
A seek() 0 11 4
A getMetadata() 0 12 5
A read() 0 25 6
A detach() 0 11 1
A isReadable() 0 2 1
A eof() 0 2 2
A getSize() 0 24 5
A isWritable() 0 2 1
A __construct() 0 14 2
A isSeekable() 0 2 1
A write() 0 19 4
A tell() 0 13 3
A rewind() 0 2 1
A getContents() 0 13 3

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.

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};
0 ignored issues
show
Bug introduced by
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...
Bug introduced by
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...
21
22
/**
23
 * @property resource|null $stream
24
 */
25
final class Stream extends StreamAbstract{
26
27
	private bool $seekable;
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