Completed
Pull Request — master (#1)
by Nikita
03:58
created

Stream   B

Complexity

Total Complexity 50

Size/Duplication

Total Lines 245
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Test Coverage

Coverage 79.1%

Importance

Changes 0
Metric Value
wmc 50
lcom 1
cbo 1
dl 0
loc 245
ccs 106
cts 134
cp 0.791
rs 8.4
c 0
b 0
f 0

29 Methods

Rating   Name   Duplication   Size   Complexity  
A hasInterceptor() 0 3 1
A setInterceptor() 0 3 1
A clearInterceptor() 0 3 1
A runUnwrapped() 0 6 1
A getCallingFile() 0 8 3
A fixPath() 0 12 3
A stream_close() 0 3 1
A stream_eof() 0 3 1
A stream_flush() 0 3 1
A stream_read() 0 3 1
A stream_seek() 0 3 1
A stream_stat() 0 3 1
A stream_tell() 0 3 1
A url_stat() 0 13 3
A dir_closedir() 0 4 1
A dir_opendir() 0 10 2
A dir_readdir() 0 3 1
A dir_rewinddir() 0 4 1
A mkdir() 0 5 1
A rename() 0 5 1
A rmdir() 0 5 1
A stream_cast() 0 3 1
A stream_lock() 0 3 1
A stream_set_option() 0 14 5
A stream_write() 0 3 1
A unlink() 0 5 1
B stream_metadata() 0 27 8
A stream_truncate() 0 3 1
A stream_open() 0 18 4

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
 * @license    http://www.opensource.org/licenses/mit-license.html
4
 *
5
 * Based on https://github.com/antecedent/patchwork/blob/master/Patchwork.php
6
 */
7
8
namespace Icewind\Interceptor;
9
10
class Stream {
11
	const STREAM_OPEN_FOR_INCLUDE = 128;
12
13
	/**
14
	 * @var Interceptor
15
	 */
16
	private static $defaultInterceptor;
17
18 4
	public static function hasInterceptor() {
19 4
		return self::$defaultInterceptor instanceof Interceptor;
20
	}
21
22 20
	public static function setInterceptor(Interceptor $interceptor) {
23 20
		self::$defaultInterceptor = $interceptor;
24 20
	}
25
26 4
	public static function clearInterceptor() {
27 4
		self::$defaultInterceptor = null;
28 4
	}
29
30
	/**
31
	 * @var resource
32
	 */
33
	public $context;
34
35
	/**
36
	 * @var resource
37
	 */
38
	public $resource;
39
40
	/**
41
	 * @param callable $callback
42
	 * @return mixed
43
	 */
44 18
	private function runUnwrapped($callback) {
45 18
		self::$defaultInterceptor->unwrap();
46 18
		$result = $callback(self::$defaultInterceptor);
47 18
		self::$defaultInterceptor->wrap();
48 18
		return $result;
49
	}
50
51
	/**
52
	 * Determine file which called stream_open() based on backtrace.
53
	 *
54
	 * @param array $backtrace
55
	 * @return string|null
56
	 */
57 1
	private function getCallingFile($backtrace) {
58 1
		foreach ($backtrace as $call) {
59 1
			if (isset($call['file'])) {
60 1
				return $call['file'];
61
			}
62 1
		}
63
		return null;
64
	}
65
66
	/**
67
	 * Check if the path is relative to the file that included it
68
	 *
69
	 * @param string $path
70
	 * @param array $backtrace
71
	 * @return string
72
	 */
73 10
	private function fixPath($path, $backtrace) {
74 10
		if ($path[0] === '/') {
75 10
			return $path;
76
		}
77 1
		$callerDir = dirname($this->getCallingFile($backtrace));
78 1
		$pathFromCallerContext = $callerDir . '/' . $path;
79 1
		if (file_exists($pathFromCallerContext)) {
80 1
			return realpath($pathFromCallerContext);
81
		} else {
82
			return $path;
83
		}
84
	}
85
86 10
	public function stream_open($path, $mode, $options) {
87 10
		$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
88
		return $this->runUnwrapped(function (Interceptor $interceptor) use ($path, $mode, $options, $backtrace) {
89 10
			$path = $this->fixPath($path, $backtrace);
90
91 10
			$including = (bool)($options & self::STREAM_OPEN_FOR_INCLUDE);
92 10
			if ($including && $interceptor->shouldIntercept($path)) {
93 3
				$this->resource = $interceptor->intercept($path);
94 3
				return true;
95
			}
96 8
			if (isset($this->context)) {
97 8
				$this->resource = fopen($path, $mode, $options, $this->context);
98 8
			} else {
99 1
				$this->resource = fopen($path, $mode, $options);
100
			}
101 8
			return $this->resource !== false;
102 10
		});
103
	}
104
105 10
	public function stream_close() {
106 10
		return fclose($this->resource);
107
	}
108
109 5
	public function stream_eof() {
110 5
		return feof($this->resource);
111
	}
112
113 10
	public function stream_flush() {
114 10
		return fflush($this->resource);
115
	}
116
117 5
	public function stream_read($count) {
118 5
		return fread($this->resource, $count);
119
	}
120
121 1
	public function stream_seek($offset, $whence = SEEK_SET) {
122 1
		return fseek($this->resource, $offset, $whence) === 0;
123
	}
124
125 4
	public function stream_stat() {
126 4
		return fstat($this->resource);
127
	}
128
129 1
	public function stream_tell() {
130 1
		return ftell($this->resource);
131
	}
132
133
	public function url_stat($path, $flags) {
134
		return $this->runUnwrapped(function () use ($path, $flags) {
135
			if ($flags & STREAM_URL_STAT_QUIET) {
136
				set_error_handler(function () {
137
				});
138
			}
139
			$result = stat($path);
140
			if ($flags & STREAM_URL_STAT_QUIET) {
141
				restore_error_handler();
142
			}
143
			return $result;
144
		});
145
	}
146
147 1
	public function dir_closedir() {
148 1
		closedir($this->resource);
149 1
		return true;
150
	}
151
152 2
	public function dir_opendir($path) {
153
		return $this->runUnwrapped(function () use ($path) {
154 2
			if (isset($this->context)) {
155 2
				$this->resource = opendir($path, $this->context);
156 2
			} else {
157
				$this->resource = opendir($path);
158
			}
159 2
			return $this->resource !== false;
160 2
		});
161
	}
162
163 2
	public function dir_readdir() {
164 2
		return readdir($this->resource);
165
	}
166
167 1
	public function dir_rewinddir() {
168 1
		rewinddir($this->resource);
169 1
		return true;
170
	}
171
172 1
	public function mkdir($path, $mode, $options) {
173
		return $this->runUnwrapped(function () use ($path, $mode, $options) {
174 1
			return mkdir($path, $mode, $options, $this->context);
175 1
		});
176
	}
177
178 1
	public function rename($pathFrom, $pathTo) {
179
		return $this->runUnwrapped(function () use ($pathFrom, $pathTo) {
180 1
			return rename($pathFrom, $pathTo, $this->context);
181 1
		});
182
	}
183
184 1
	public function rmdir($path) {
185
		return $this->runUnwrapped(function () use ($path) {
186 1
			return rmdir($path, $this->context);
187 1
		});
188
	}
189
190
	public function stream_cast() {
191
		return $this->resource;
192
	}
193
194 1
	public function stream_lock($operation) {
195 1
		return flock($this->resource, $operation);
196
	}
197
198 1
	public function stream_set_option($option, $arg1, $arg2) {
199
		switch ($option) {
200 1
			case STREAM_OPTION_BLOCKING:
201 1
				return stream_set_blocking($this->resource, $arg1);
202 1
			case STREAM_OPTION_READ_TIMEOUT:
203 1
				return stream_set_timeout($this->resource, $arg1, $arg2);
204 1
			case STREAM_OPTION_WRITE_BUFFER:
205 1
				return stream_set_write_buffer($this->resource, $arg1);
206
			case STREAM_OPTION_READ_BUFFER:
207
				return stream_set_read_buffer($this->resource, $arg1);
208
			default:
209
				throw new \InvalidArgumentException();
210
		}
211
	}
212
213 1
	public function stream_write($data) {
214 1
		return fwrite($this->resource, $data);
215
	}
216
217 1
	public function unlink($path) {
218
		return $this->runUnwrapped(function () use ($path) {
219 1
			return unlink($path, $this->context);
220 1
		});
221
	}
222
223
	public function stream_metadata($path, $option, $value) {
224 2
		return $this->runUnwrapped(function () use ($path, $option, $value) {
225
			switch ($option) {
226 2
				case STREAM_META_TOUCH:
227 1
					if (empty($value)) {
228 1
						$result = touch($path);
229 1
					} else {
230
						$result = touch($path, $value[0], $value[1]);
231
					}
232 1
					break;
233 1
				case STREAM_META_OWNER_NAME:
234 1
				case STREAM_META_OWNER:
235
					$result = chown($path, $value);
236
					break;
237 1
				case STREAM_META_GROUP_NAME:
238 1
				case STREAM_META_GROUP:
239
					$result = chgrp($path, $value);
240
					break;
241 1
				case STREAM_META_ACCESS:
242 1
					$result = chmod($path, $value);
243 1
					break;
244
				default:
245
					throw new \InvalidArgumentException();
246
			}
247 2
			return $result;
248 2
		});
249
	}
250
251 1
	public function stream_truncate($new_size) {
252 1
		return ftruncate($this->resource, $new_size);
253
	}
254
}
255