Passed
Push — gh-pages ( 22b0fe...eb2d91 )
by
unknown
12:27 queued 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 seek() 0 11 4
A getMetadata() 0 12 5
A read() 0 25 6
A write() 0 19 4
A tell() 0 13 3
A getContents() 0 13 3
A close() 0 7 2
A __toString() 0 17 4
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 rewind() 0 2 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.

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