Completed
Pull Request — master (#243)
by lucky
05:45
created

MonkeyPatchManager::setPatcherList()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 4

Duplication

Lines 9
Ratio 100 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 1
dl 9
loc 9
rs 9.6666
c 0
b 0
f 0
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 View Code Duplication
class MonkeyPatchManager
0 ignored issues
show
Duplication introduced by
This class seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
19
{
20
	public static $debug = false;
21
22
	private static $php_parser = ParserFactory::PREFER_PHP5;
23
24
	/**
25
	 * The path to the log file if `$debug` is true.
26
	 * Will be set in {@link MonkeyPatchManager::setDebug}.
27
	 * @var string|null */
28
	public static $log_file = null;
29
	private static $load_patchers = false;
30
	private static $exit_exception_classname =
31
		'Kenjis\MonkeyPatch\Exception\ExitException';
32
	/**
33
	 * @var array list of patcher classname
34
	 */
35
	private static $patcher_list = [
36
		'ExitPatcher',
37
		'FunctionPatcher',
38
		'MethodPatcher',
39
		'ConstantPatcher',
40
	];
41
42
	public static function log($message)
43
	{
44
		if (! self::$debug)
45
		{
46
			return;
47
		}
48
49
		$time = date('Y-m-d H:i:s');
50
		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...
51
		$usec = substr($usec, 1);
52
		$log = "[$time $usec] $message\n";
53
		file_put_contents(self::$log_file, $log, FILE_APPEND);
54
	}
55
56
	public static function setExitExceptionClassname($name)
57
	{
58
		self::$exit_exception_classname = $name;
59
	}
60
61
	public static function getExitExceptionClassname()
62
	{
63
		return self::$exit_exception_classname;
64
	}
65
66
	public static function getPhpParser()
67
	{
68
		return self::$php_parser;
69
	}
70
71
	protected static function setDebug(array $config)
72
	{
73
		if (isset($config['debug']))
74
		{
75
			self::$debug = $config['debug'];
76
		}
77
		if (isset($config['log_file']))
78
		{
79
			self::$debug = $config['log_file'];
80
		}
81
		if (is_null(self::$log_file))
82
		{
83
			self::$log_file = __DIR__ . '/debug.log';
84
		}
85
	}
86
87
	protected static function setDir(array $config)
88
	{
89
		if (isset($config['root_dir']))
90
		{
91
			Cache::setProjectRootDir($config['root_dir']);
92
		}
93
		else
94
		{
95
			// APPPATH is constant in CodeIgniter
96
			Cache::setProjectRootDir(APPPATH . '../');
97
		}
98
99
		if (! isset($config['cache_dir']))
100
		{
101
			throw new LogicException('You have to set "cache_dir"');
102
		}
103
		self::setCacheDir($config['cache_dir']);
104
	}
105
106
	protected static function setPaths(array $config)
107
	{
108
		if (! isset($config['include_paths']))
109
		{
110
			throw new LogicException('You have to set "include_paths"');
111
		}
112
		self::setIncludePaths($config['include_paths']);
113
114
		if (isset($config['exclude_paths']))
115
		{
116
			self::setExcludePaths($config['exclude_paths']);
117
		}
118
	}
119
120
	public static function init(array $config)
121
	{
122
		self::setDebug($config);
123
124
		if (isset($config['php_parser']))
125
		{
126
			self::$php_parser = constant('PhpParser\ParserFactory::'.$config['php_parser']);
127
		}
128
129
		self::setDir($config);
130
		self::setPaths($config);
131
132
		Cache::createTmpListDir();
133
134
		if (isset($config['patcher_list']))
135
		{
136
			self::setPatcherList($config['patcher_list']);
137
		}
138
		self::checkPatcherListUpdate();
139
		self::checkPathsUpdate();
140
141
		self::loadPatchers();
142
143
		self::addTmpFunctionBlacklist();
144
145
		if (isset($config['functions_to_patch']))
146
		{
147
			FunctionPatcher::addWhitelists($config['functions_to_patch']);
148
		}
149
		self::checkFunctionWhitelistUpdate();
150
		FunctionPatcher::lockFunctionList();
151
152
		if (isset($config['exit_exception_classname']))
153
		{
154
			self::setExitExceptionClassname($config['exit_exception_classname']);
155
		}
156
157
		// Register include stream wrapper for monkey patching
158
		self::wrap();
159
	}
160
161
	protected static function checkPathsUpdate()
162
	{
163
		$cached = Cache::getTmpIncludePaths();
164
		$current = PathChecker::getIncludePaths();
165
166
		// Updated?
167
		if ($cached !== $current)
168
		{
169
			MonkeyPatchManager::log('clear_src_cache: from ' . __METHOD__);
170
			Cache::clearSrcCache();
171
			Cache::writeTmpIncludePaths($current);
172
		}
173
174
		$cached = Cache::getTmpExcludePaths();
175
		$current = PathChecker::getExcludePaths();
176
177
		// Updated?
178
		if ($cached !== $current)
179
		{
180
			MonkeyPatchManager::log('clear_src_cache: from ' . __METHOD__);
181
			Cache::clearSrcCache();
182
			Cache::writeTmpExcludePaths($current);
183
		}
184
	}
185
186
	protected static function checkPatcherListUpdate()
187
	{
188
		$cached = Cache::getTmpPatcherList();
189
190
		// Updated?
191
		if ($cached !== self::$patcher_list)
192
		{
193
			MonkeyPatchManager::log('clear_src_cache: from ' . __METHOD__);
194
			Cache::clearSrcCache();
195
			Cache::writeTmpPatcherList(self::$patcher_list);
196
		}
197
	}
198
199
	protected static function checkFunctionWhitelistUpdate()
200
	{
201
		$cached = Cache::getTmpFunctionWhitelist();
202
		$current = FunctionPatcher::getFunctionWhitelist();
203
204
		// Updated?
205
		if ($cached !== $current)
206
		{
207
			MonkeyPatchManager::log('clear_src_cache: from ' . __METHOD__);
208
			Cache::clearSrcCache();
209
			Cache::writeTmpFunctionWhitelist($current);
210
		}
211
	}
212
213
	protected static function addTmpFunctionBlacklist()
214
	{
215
		$list = file(Cache::getTmpFunctionBlacklistFile());
216
		foreach ($list as $function)
217
		{
218
			FunctionPatcher::addBlacklist(trim($function));
219
		}
220
	}
221
222
	public static function isEnabled($patcher)
223
	{
224
		return in_array($patcher, self::$patcher_list);
225
	}
226
227
	public static function setPatcherList(array $list)
228
	{
229
		if (self::$load_patchers)
230
		{
231
			throw new LogicException("Can't change patcher list after initialisation");
232
		}
233
234
		self::$patcher_list = $list;
235
	}
236
237
	public static function setCacheDir($dir)
238
	{
239
		Cache::setCacheDir($dir);
240
	}
241
242
	public static function setIncludePaths(array $dir_list)
243
	{
244
		PathChecker::setIncludePaths($dir_list);
245
	}
246
247
	public static function setExcludePaths(array $dir_list)
248
	{
249
		PathChecker::setExcludePaths($dir_list);
250
	}
251
252
	public static function wrap()
253
	{
254
		IncludeStream::wrap();
255
	}
256
257
	public static function unwrap()
258
	{
259
		IncludeStream::unwrap();
260
	}
261
262
	/**
263
	 * @param string $path original source file path
264
	 * @return resource
265
	 * @throws LogicException
266
	 */
267
	public static function patch($path)
268
	{
269
		if (! is_readable($path))
270
		{
271
			throw new LogicException("Can't read '$path'");
272
		}
273
274
		// Check cache file
275
		if ($cache_file = Cache::getValidSrcCachePath($path))
276
		{
277
			self::log('cache_hit: ' . $path);
278
			return fopen($cache_file, 'r');
279
		}
280
281
		self::log('cache_miss: ' . $path);
282
		$source = file_get_contents($path);
283
284
		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...
285
286
		// Write to cache file
287
		self::log('write_cache: ' . $path);
288
		Cache::writeSrcCacheFile($path, $new_source);
289
290
		$resource = fopen('php://memory', 'rb+');
291
		fwrite($resource, $new_source);
292
		rewind($resource);
293
		return $resource;
294
	}
295
296
	protected static function loadPatchers()
297
	{
298
		if (self::$load_patchers)
299
		{
300
			return;
301
		}
302
303
		require __DIR__ . '/Patcher/AbstractPatcher.php';
304
		require __DIR__ . '/Patcher/Backtrace.php';
305
306
		foreach (self::$patcher_list as $classname)
307
		{
308
			require __DIR__ . '/Patcher/' . $classname . '.php';
309
		}
310
311
		self::$load_patchers = true;
312
	}
313
314
	protected static function execPatchers($source)
315
	{
316
		$patched = false;
317
		foreach (self::$patcher_list as $classname)
318
		{
319
			$classname = 'Kenjis\MonkeyPatch\Patcher\\' . $classname;
320
			$patcher = new $classname;
321
			list($source, $patched_this) = $patcher->patch($source);
322
			$patched = $patched || $patched_this;
323
		}
324
325
		return [
326
			$source,
327
			$patched,
328
		];
329
	}
330
}
331