| 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 |