Complex classes like InternalCallMapHandler 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 InternalCallMapHandler, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
24 | class InternalCallMapHandler |
||
25 | { |
||
26 | const PHP_MAJOR_VERSION = 7; |
||
27 | const PHP_MINOR_VERSION = 4; |
||
28 | const LOWEST_AVAILABLE_DELTA = 71; |
||
29 | |||
30 | /** |
||
31 | * @var ?int |
||
32 | */ |
||
33 | private static $loaded_php_major_version = null; |
||
34 | /** |
||
35 | * @var ?int |
||
36 | */ |
||
37 | private static $loaded_php_minor_version = null; |
||
38 | |||
39 | /** |
||
40 | * @var array<array<int|string,string>>|null |
||
41 | */ |
||
42 | private static $call_map = null; |
||
43 | |||
44 | /** |
||
45 | * @var array<array<int, TCallable>>|null |
||
46 | */ |
||
47 | private static $call_map_callables = []; |
||
48 | |||
49 | /** |
||
50 | * @var array<string, list<list<Type\TaintKind::*>>> |
||
51 | */ |
||
52 | private static $taint_sink_map = []; |
||
53 | |||
54 | /** |
||
55 | * @param string $method_id |
||
56 | * @param array<int, PhpParser\Node\Arg> $args |
||
57 | * |
||
58 | * @return TCallable |
||
59 | */ |
||
60 | public static function getCallableFromCallMapById( |
||
81 | |||
82 | /** |
||
83 | * @param array<int, TCallable> $callables |
||
84 | * @param array<int, PhpParser\Node\Arg> $args |
||
85 | * |
||
86 | * @return TCallable |
||
87 | */ |
||
88 | public static function getMatchingCallableFromCallMapOptions( |
||
219 | |||
220 | /** |
||
221 | * @param string $function_id |
||
222 | * |
||
223 | * @return array|null |
||
224 | * @psalm-return array<int, TCallable>|null |
||
225 | */ |
||
226 | public static function getCallablesFromCallMap($function_id) |
||
227 | { |
||
228 | $call_map_key = strtolower($function_id); |
||
229 | |||
230 | if (isset(self::$call_map_callables[$call_map_key])) { |
||
231 | return self::$call_map_callables[$call_map_key]; |
||
232 | } |
||
233 | |||
234 | $call_map = self::getCallMap(); |
||
235 | |||
236 | if (!isset($call_map[$call_map_key])) { |
||
237 | return null; |
||
238 | } |
||
239 | |||
240 | $call_map_functions = []; |
||
241 | $call_map_functions[] = $call_map[$call_map_key]; |
||
242 | |||
243 | for ($i = 1; $i < 10; ++$i) { |
||
244 | if (!isset($call_map[$call_map_key . '\'' . $i])) { |
||
245 | break; |
||
246 | } |
||
247 | |||
248 | $call_map_functions[] = $call_map[$call_map_key . '\'' . $i]; |
||
249 | } |
||
250 | |||
251 | $possible_callables = []; |
||
252 | |||
253 | foreach ($call_map_functions as $call_map_function_args) { |
||
254 | $return_type_string = array_shift($call_map_function_args); |
||
255 | |||
256 | if (!$return_type_string) { |
||
257 | $return_type = Type::getMixed(); |
||
258 | } else { |
||
259 | $return_type = Type::parseString($return_type_string); |
||
260 | } |
||
261 | |||
262 | $function_params = []; |
||
263 | |||
264 | $arg_offset = 0; |
||
265 | |||
266 | /** @var string $arg_name - key type changed with above array_shift */ |
||
267 | foreach ($call_map_function_args as $arg_name => $arg_type) { |
||
268 | $by_reference = false; |
||
269 | $optional = false; |
||
270 | $variadic = false; |
||
271 | |||
272 | if ($arg_name[0] === '&') { |
||
273 | $arg_name = substr($arg_name, 1); |
||
274 | $by_reference = true; |
||
275 | } |
||
276 | |||
277 | if (substr($arg_name, -1) === '=') { |
||
278 | $arg_name = substr($arg_name, 0, -1); |
||
279 | $optional = true; |
||
280 | } |
||
281 | |||
282 | if (substr($arg_name, 0, 3) === '...') { |
||
283 | $arg_name = substr($arg_name, 3); |
||
284 | $variadic = true; |
||
285 | } |
||
286 | |||
287 | $param_type = $arg_type |
||
288 | ? Type::parseString($arg_type) |
||
289 | : Type::getMixed(); |
||
290 | |||
291 | $out_type = null; |
||
292 | |||
293 | if (\strlen($arg_name) > 2 && $arg_name[0] === 'w' && $arg_name[1] === '_') { |
||
294 | $out_type = $param_type; |
||
295 | $param_type = Type::getMixed(); |
||
296 | } |
||
297 | |||
298 | $function_param = new FunctionLikeParameter( |
||
299 | $arg_name, |
||
300 | $by_reference, |
||
301 | $param_type, |
||
302 | null, |
||
303 | null, |
||
304 | $optional, |
||
305 | false, |
||
306 | $variadic |
||
307 | ); |
||
308 | |||
309 | if ($out_type) { |
||
310 | $function_param->out_type = $out_type; |
||
311 | } |
||
312 | |||
313 | if (isset(self::$taint_sink_map[$call_map_key][$arg_offset])) { |
||
314 | $function_param->sinks = self::$taint_sink_map[$call_map_key][$arg_offset]; |
||
315 | } |
||
316 | |||
317 | $function_param->signature_type = null; |
||
318 | |||
319 | $function_params[] = $function_param; |
||
320 | |||
321 | $arg_offset++; |
||
322 | } |
||
323 | |||
324 | $possible_callables[] = new TCallable('callable', $function_params, $return_type); |
||
325 | } |
||
326 | |||
327 | self::$call_map_callables[$call_map_key] = $possible_callables; |
||
328 | |||
329 | return $possible_callables; |
||
330 | } |
||
331 | |||
332 | /** |
||
333 | * Gets the method/function call map |
||
334 | * |
||
335 | * @return array<string, array<int|string, string>> |
||
|
|||
336 | * @psalm-suppress MixedInferredReturnType as the use of require buggers things up |
||
337 | * @psalm-suppress MixedTypeCoercion |
||
338 | * @psalm-suppress MixedReturnStatement |
||
339 | */ |
||
340 | public static function getCallMap() |
||
412 | |||
413 | /** |
||
414 | * @param string $key |
||
415 | * |
||
416 | * @return bool |
||
417 | */ |
||
418 | public static function inCallMap($key) |
||
422 | } |
||
423 |
This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.