| Total Complexity | 42 | 
| Total Lines | 118 | 
| Duplicated Lines | 0 % | 
| Changes | 1 | ||
| Bugs | 0 | Features | 1 | 
Complex classes like ClassMethodArgVisitor 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.
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 ClassMethodArgVisitor, and based on these observations, apply Extract Interface, too.
| 1 | <?php | ||
| 16 | class ClassMethodArgVisitor extends NodeVisitorAbstract | ||
| 17 | { | ||
| 18 | private $keys = []; | ||
| 19 | |||
| 20 | private $filePath; | ||
| 21 | |||
| 22 | private $className; | ||
| 23 | |||
| 24 | private $classArgposClassesMap = []; | ||
| 25 | |||
| 26 | private $config; | ||
| 27 | |||
| 28 | public function __construct(array &$keys, string $filePath, array $config) | ||
| 29 |     { | ||
| 30 | $this->keys = &$keys; | ||
| 31 | $this->filePath = $filePath; | ||
| 32 | $this->className = (string)pathinfo($filePath, PATHINFO_FILENAME); | ||
| 33 | $this->config = $config; | ||
| 34 | } | ||
| 35 | |||
| 36 | public function enterNode(Node $node) | ||
| 37 |     { | ||
| 38 | $this->prepareUseClasses($node); | ||
| 39 | // find new Class(arg1, arg2, arg3) | ||
| 40 |         if ($node instanceof New_ && isset($node->class) && in_array($node->class->name ?? null, $this->classArgposClassesMap, true)) { | ||
|  | |||
| 41 | $className = $node->class->name; | ||
| 42 | $argIndex = array_search($className, $this->classArgposClassesMap); | ||
| 43 | $args = $node->args; | ||
| 44 |             if (isset($args[$argIndex]) && $args[$argIndex]->value instanceof String_) { | ||
| 45 | $key = $args[$argIndex]->value->value; | ||
| 46 | $this->addKey($args[$argIndex]->getStartLine(), $className, $key); | ||
| 47 | } | ||
| 48 | } | ||
| 49 | // find in Class ->method(arg1, arg2, arg3) | ||
| 50 | if ($node instanceof MethodCall && | ||
| 51 |             $node->name instanceof Identifier) { | ||
| 52 | $methodName = $node->name->name; | ||
| 53 |             foreach ($this->config['CLASS_ARGPOS_METHODS'] ?? [] as $classNamePart => $argposMethods) { | ||
| 54 | // ALL classes OR Classes end with classNamePart | ||
| 55 |                 if ($classNamePart !== 'ALL' && (strpos($this->className, $classNamePart) === false || substr($this->className, -strlen($classNamePart)) !== $classNamePart)) { | ||
| 56 | continue; | ||
| 57 | } | ||
| 58 |                 foreach ($argposMethods as $argIndex => $methods) { | ||
| 59 |                     if (in_array($methodName, $methods, true)) { | ||
| 60 | $this->extractKeyFromArgument($node, $argIndex, $classNamePart); | ||
| 61 | } | ||
| 62 | } | ||
| 63 | } | ||
| 64 | } | ||
| 65 | } | ||
| 66 | |||
| 67 | private function prepareUseClasses(Node $node) | ||
| 76 | } | ||
| 77 | } | ||
| 78 | } | ||
| 79 | } | ||
| 80 | } | ||
| 81 | |||
| 82 | private function extractKeyFromArgument(MethodCall $node, int $argIndex, string $classNamePart): void | ||
| 83 |     { | ||
| 84 | $args = $node->args; | ||
| 85 | // find in funciton return array values | ||
| 86 | if (isset($args[$argIndex]) && $args[$argIndex]->value instanceof Closure && | ||
| 87 | isset($args[$argIndex]->value) && isset($args[$argIndex]->value) | ||
| 88 |         ) { | ||
| 89 | $method = $node->name->name; | ||
| 90 | $closure = $args[$argIndex]->value; | ||
| 91 |             if ($closure->stmts !== null) { | ||
| 92 | $return = reset($closure->stmts); | ||
| 93 |                 if ($return instanceof Return_ && $return->expr instanceof Array_ && $return->expr->items !== null) { | ||
| 94 | $items = $return->expr->items; | ||
| 95 |                     foreach ($items as $item) { | ||
| 96 |                         if ($item->value instanceof String_) { | ||
| 97 | $key = $item->value->value; | ||
| 98 |                             $this->addKey($item->value->getAttribute('startLine'), $method, $key, null); | ||
| 99 | } | ||
| 100 | } | ||
| 101 | } | ||
| 102 | } | ||
| 103 | } | ||
| 104 | |||
| 105 |         if (isset($args[$argIndex]) && $args[$argIndex]->value instanceof String_) { | ||
| 106 | $method = $node->name->name; | ||
| 107 | $arg = null; | ||
| 108 |             if ($method === 'translate' && isset($args[$argIndex + 1]) && $args[$argIndex + 1]->value instanceof Node\Expr\Array_) { | ||
| 109 | $arg = $args[$argIndex + 1]->value->items[0]->key->value; | ||
| 110 | } | ||
| 111 | |||
| 112 | $key = $args[$argIndex]->value->value; | ||
| 113 | $allowEmptyTranslation = $this->config['ALLOW_EMPTY_TRANSLATION'] ?? []; | ||
| 114 | if ( | ||
| 115 | array_key_exists($classNamePart, $allowEmptyTranslation) && | ||
| 116 | array_key_exists($argIndex, $allowEmptyTranslation[$classNamePart]) && | ||
| 117 | ($key === '' || $key === '--') && | ||
| 118 | (in_array($method, $allowEmptyTranslation[$classNamePart][$argIndex], true)) | ||
| 119 |             ) { | ||
| 120 | return; | ||
| 121 | } | ||
| 122 | $this->addKey($args[$argIndex]->getStartLine(), $method, $key, $arg); | ||
| 123 | } | ||
| 124 | } | ||
| 125 | |||
| 126 | private function addKey(int $line, string $call, string $key, string $arg = null): void | ||
| 134 | ]; | ||
| 135 | } | ||
| 136 | } | ||
| 137 |