@@ 18-330 (lines=313) @@ | ||
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 | /** |
|
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()); |
|
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); |
|
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 |
@@ 18-330 (lines=313) @@ | ||
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 | /** |
|
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()); |
|
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 (self::$debug && 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); |
|
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 |