Completed
Push — master ( 6fedc8...7091fd )
by Kenji
02:52
created

MonkeyPatchManager::setPaths()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 13
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 13
rs 9.4286
cc 3
eloc 6
nc 3
nop 1
1
<?php
2
/**
3
 * Part of CI PHPUnit Test
4
 *
5
 * @author     Kenji Suzuki <https://github.com/kenjis>
6
 * @license    MIT License
7
 * @copyright  2015 Kenji Suzuki
8
 * @link       https://github.com/kenjis/ci-phpunit-test
9
 */
10
11
namespace Kenjis\MonkeyPatch;
12
13
use LogicException;
14
use RuntimeException;
15
use PhpParser\ParserFactory;
16
use Kenjis\MonkeyPatch\Patcher\FunctionPatcher;
17
18
class MonkeyPatchManager
19
{
20
	public static $debug = false;
21
22
	private static $php_parser = ParserFactory::PREFER_PHP5;
23
24
	private static $log_file;
25
	private static $load_patchers = false;
26
	private static $exit_exception_classname = 
27
		'Kenjis\MonkeyPatch\Exception\ExitException';
28
	/**
29
	 * @var array list of patcher classname
30
	 */
31
	private static $patcher_list = [
32
		'ExitPatcher',
33
		'FunctionPatcher',
34
		'MethodPatcher',
35
	];
36
37
	public static function log($message)
38
	{
39
		if (! self::$debug)
40
		{
41
			return;
42
		}
43
44
		$time = date('Y-m-d H:i:s');
45
		list($usec, $sec) = explode(' ', microtime());
0 ignored issues
show
Unused Code introduced by
The assignment to $sec is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
46
		$usec = substr($usec, 1);
47
		$log = "[$time $usec] $message\n";
48
		file_put_contents(self::$log_file, $log, FILE_APPEND);
49
	}
50
51
	public static function setExitExceptionClassname($name)
52
	{
53
		self::$exit_exception_classname = $name;
54
	}
55
56
	public static function getExitExceptionClassname()
57
	{
58
		return self::$exit_exception_classname;
59
	}
60
61
	public static function getPhpParser()
62
	{
63
		return self::$php_parser;
64
	}
65
66
	protected static function setDebug(array $config)
67
	{
68
		if (isset($config['debug']))
69
		{
70
			self::$debug = $config['debug'];
71
		}
72
		if (self::$debug)
73
		{
74
			self::$log_file = __DIR__ . '/debug.log';
75
		}
76
	}
77
78
	protected static function setDir(array $config)
79
	{
80
		if (isset($config['root_dir']))
81
		{
82
			Cache::setProjectRootDir($config['root_dir']);
83
		}
84
		else
85
		{
86
			// APPPATH is constant in CodeIgniter
87
			Cache::setProjectRootDir(APPPATH . '../');
88
		}
89
90
		if (! isset($config['cache_dir']))
91
		{
92
			throw new LogicException('You have to set "cache_dir"');
93
		}
94
		self::setCacheDir($config['cache_dir']);
95
	}
96
97
	protected static function setPaths(array $config)
98
	{
99
		if (! isset($config['include_paths']))
100
		{
101
			throw new LogicException('You have to set "include_paths"');
102
		}
103
		self::setIncludePaths($config['include_paths']);
104
105
		if (isset($config['exclude_paths']))
106
		{
107
			self::setExcludePaths($config['exclude_paths']);
108
		}
109
	}
110
111
	public static function init(array $config)
112
	{
113
		self::setDebug($config);
114
115
		if (isset($config['php_parser']))
116
		{
117
			self::$php_parser = constant('PhpParser\ParserFactory::'.$config['php_parser']);
118
		}
119
120
		self::setDir($config);
121
		self::setPaths($config);
122
123
		Cache::createTmpListDir();
124
125
		if (isset($config['patcher_list']))
126
		{
127
			self::setPatcherList($config['patcher_list']);
128
		}
129
		self::checkPatcherListUpdate();
130
		self::checkPathsUpdate();
131
132
		self::loadPatchers();
133
134
		self::addTmpFunctionBlacklist();
135
136
		if (isset($config['functions_to_patch']))
137
		{
138
			FunctionPatcher::addWhitelists($config['functions_to_patch']);
139
		}
140
		self::checkFunctionWhitelistUpdate();
141
		FunctionPatcher::lockFunctionList();
142
143
		if (isset($config['exit_exception_classname']))
144
		{
145
			self::setExitExceptionClassname($config['exit_exception_classname']);
146
		}
147
148
		// Register include stream wrapper for monkey patching
149
		self::wrap();
150
	}
151
152
	protected static function checkPathsUpdate()
153
	{
154
		$cached = Cache::getTmpIncludePaths();
155
		$current = PathChecker::getIncludePaths();
156
157
		// Updated?
158
		if ($cached !== $current)
159
		{
160
			MonkeyPatchManager::log('clear_src_cache: from ' . __METHOD__);
161
			Cache::clearSrcCache();
162
			Cache::writeTmpIncludePaths($current);
163
		}
164
165
		$cached = Cache::getTmpExcludePaths();
166
		$current = PathChecker::getExcludePaths();
167
168
		// Updated?
169
		if ($cached !== $current)
170
		{
171
			MonkeyPatchManager::log('clear_src_cache: from ' . __METHOD__);
172
			Cache::clearSrcCache();
173
			Cache::writeTmpExcludePaths($current);
174
		}
175
	}
176
177
	protected static function checkPatcherListUpdate()
178
	{
179
		$cached = Cache::getTmpPatcherList();
180
181
		// Updated?
182
		if ($cached !== self::$patcher_list)
183
		{
184
			MonkeyPatchManager::log('clear_src_cache: from ' . __METHOD__);
185
			Cache::clearSrcCache();
186
			Cache::writeTmpPatcherList(self::$patcher_list);
187
		}
188
	}
189
190
	protected static function checkFunctionWhitelistUpdate()
191
	{
192
		$cached = Cache::getTmpFunctionWhitelist();
193
		$current = FunctionPatcher::getFunctionWhitelist();
194
195
		// Updated?
196
		if ($cached !== $current)
197
		{
198
			MonkeyPatchManager::log('clear_src_cache: from ' . __METHOD__);
199
			Cache::clearSrcCache();
200
			Cache::writeTmpFunctionWhitelist($current);
201
		}
202
	}
203
204
	protected static function addTmpFunctionBlacklist()
205
	{
206
		$list = file(Cache::getTmpFunctionBlacklistFile());
207
		foreach ($list as $function)
208
		{
209
			FunctionPatcher::addBlacklist(trim($function));
210
		}
211
	}
212
213
	public static function isEnabled($patcher)
214
	{
215
		return in_array($patcher, self::$patcher_list);
216
	}
217
218
	public static function setPatcherList(array $list)
219
	{
220
		if (self::$load_patchers)
221
		{
222
			throw new LogicException("Can't change patcher list after initialisation");
223
		}
224
225
		self::$patcher_list = $list;
226
	}
227
228
	public static function setCacheDir($dir)
229
	{
230
		Cache::setCacheDir($dir);
231
	}
232
233
	public static function setIncludePaths(array $dir_list)
234
	{
235
		PathChecker::setIncludePaths($dir_list);
236
	}
237
238
	public static function setExcludePaths(array $dir_list)
239
	{
240
		PathChecker::setExcludePaths($dir_list);
241
	}
242
243
	public static function wrap()
244
	{
245
		IncludeStream::wrap();
246
	}
247
248
	public static function unwrap()
249
	{
250
		IncludeStream::unwrap();
251
	}
252
253
	/**
254
	 * @param string $path original source file path
255
	 * @return resource
256
	 * @throws LogicException
257
	 */
258
	public static function patch($path)
259
	{
260
		if (! is_readable($path))
261
		{
262
			throw new LogicException("Can't read '$path'");
263
		}
264
265
		// Check cache file
266
		if ($cache_file = Cache::getValidSrcCachePath($path))
267
		{
268
			self::log('cache_hit: ' . $path);
269
			return fopen($cache_file, 'r');
270
		}
271
272
		self::log('cache_miss: ' . $path);
273
		$source = file_get_contents($path);
274
275
		list($new_source, $patched) = self::execPatchers($source);
0 ignored issues
show
Unused Code introduced by
The assignment to $patched is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
276
277
		// Write to cache file
278
		self::log('write_cache: ' . $path);
279
		Cache::writeSrcCacheFile($path, $new_source);
280
281
		$resource = fopen('php://memory', 'rb+');
282
		fwrite($resource, $new_source);
283
		rewind($resource);
284
		return $resource;
285
	}
286
287
	protected static function loadPatchers()
288
	{
289
		if (self::$load_patchers)
290
		{
291
			return;
292
		}
293
294
		require __DIR__ . '/Patcher/AbstractPatcher.php';
295
		require __DIR__ . '/Patcher/Backtrace.php';
296
297
		foreach (self::$patcher_list as $classname)
298
		{
299
			require __DIR__ . '/Patcher/' . $classname . '.php';
300
		}
301
302
		self::$load_patchers = true;
303
	}
304
305
	protected static function execPatchers($source)
306
	{
307
		$patched = false;
308
		foreach (self::$patcher_list as $classname)
309
		{
310
			$classname = 'Kenjis\MonkeyPatch\Patcher\\' . $classname;
311
			$patcher = new $classname;
312
			list($source, $patched_this) = $patcher->patch($source);
313
			$patched = $patched || $patched_this;
314
		}
315
316
		return [
317
			$source,
318
			$patched,
319
		];
320
	}
321
}
322