Complex classes like CodeHandler 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 CodeHandler, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 8 | class CodeHandler extends CompilerUtils |
||
| 9 | { |
||
| 10 | protected $input; |
||
| 11 | protected $name; |
||
| 12 | protected $separators; |
||
| 13 | |||
| 14 | public function __construct($input, $name) |
||
| 15 | { |
||
| 16 | if (!is_string($input)) { |
||
| 17 | throw new \InvalidArgumentException('Expecting a string of PHP, got: ' . gettype($input), 11); |
||
| 18 | } |
||
| 19 | |||
| 20 | if (strlen($input) === 0) { |
||
| 21 | throw new \InvalidArgumentException('Expecting a string of PHP, empty string received.', 12); |
||
| 22 | } |
||
| 23 | |||
| 24 | $this->input = trim(preg_replace('/\bvar\b/', '', $input)); |
||
| 25 | $this->name = $name; |
||
| 26 | $this->separators = array(); |
||
| 27 | } |
||
| 28 | |||
| 29 | public function innerCode($input, $name) |
||
| 30 | { |
||
| 31 | $handler = new static($input, $name); |
||
| 32 | |||
| 33 | return $handler->parse(); |
||
| 34 | } |
||
| 35 | |||
| 36 | public function parse() |
||
| 37 | { |
||
| 38 | if ($this->isQuotedString()) { |
||
| 39 | return array($this->input); |
||
| 40 | } |
||
| 41 | |||
| 42 | if (strpos('=,;?', substr($this->input, 0, 1)) !== false) { |
||
| 43 | throw new \ErrorException('Expecting a variable name or an expression, got: ' . $this->input, 13); |
||
| 44 | } |
||
| 45 | |||
| 46 | preg_match_all( |
||
| 47 | '/(?<![<>=!])=(?!>|=)|[\[\]\{\}\(\),;\.]|(?!:):|->/', // punctuation |
||
| 48 | preg_replace_callback('/[a-zA-Z0-9\\\\_\\x7f-\\xff]*\((?:[0-9\/%\.\s*+-]++|(?R))*+\)/', function ($match) { |
||
| 49 | // no need to keep separators in simple PHP expressions (functions calls, parentheses, calculs) |
||
| 50 | return str_repeat(' ', strlen($match[0])); |
||
| 51 | }, preg_replace_callback('/([\'"]).*?(?<!\\\\)(?:\\\\{2})*\\1/', function ($match) { |
||
| 52 | // do not take separators in strings |
||
| 53 | return str_repeat(' ', strlen($match[0])); |
||
| 54 | }, $this->input)), |
||
| 55 | $separators, |
||
| 56 | PREG_PATTERN_ORDER | PREG_OFFSET_CAPTURE |
||
| 57 | ); |
||
| 58 | |||
| 59 | $this->separators = $separators[0]; |
||
| 60 | |||
| 61 | if (count($this->separators) === 0) { |
||
| 62 | if (strstr('0123456789-+("\'$', substr($this->input, 0, 1)) === false) { |
||
| 63 | $this->input = static::addDollarIfNeeded($this->input); |
||
| 64 | } |
||
| 65 | |||
| 66 | return array($this->input); |
||
| 67 | } |
||
| 68 | |||
| 69 | // add a pseudo separator for the end of the input |
||
| 70 | array_push($this->separators, array(null, strlen($this->input))); |
||
| 71 | |||
| 72 | return $this->parseBetweenSeparators(); |
||
| 73 | } |
||
| 74 | |||
| 75 | protected function isQuotedString() |
||
| 76 | { |
||
| 77 | $firstChar = substr($this->input, 0, 1); |
||
| 78 | $lastChar = substr($this->input, -1); |
||
| 79 | |||
| 80 | return false !== strpos('"\'', $firstChar) && $lastChar === $firstChar; |
||
| 81 | } |
||
| 82 | |||
| 83 | protected function getVarname($separator) |
||
| 84 | { |
||
| 85 | // do not add $ if it is not like a variable |
||
| 86 | $varname = static::convertVarPath(substr($this->input, 0, $separator[1]), '/^%s/'); |
||
| 87 | |||
| 88 | return $separator[0] !== '(' && $varname !== '' && strstr('0123456789-+("\'$', substr($varname, 0, 1)) === false |
||
| 89 | ? static::addDollarIfNeeded($varname) |
||
| 90 | : $varname; |
||
| 91 | } |
||
| 92 | |||
| 93 | protected function parseArrayString(&$argument, $match, $consume, &$quote, &$key, &$value) |
||
| 94 | { |
||
| 95 | $quote = $quote |
||
| 96 | ? CommonUtils::escapedEnd($match[1]) |
||
| 97 | ? $quote |
||
| 98 | : null |
||
| 99 | : $match[2]; |
||
| 100 | ${is_null($value) ? 'key' : 'value'} .= $match[0]; |
||
| 101 | $consume($argument, $match[0]); |
||
| 102 | } |
||
| 103 | |||
| 104 | protected function parseArrayAssign(&$argument, $match, $consume, &$quote, &$key, &$value) |
||
| 105 | { |
||
| 106 | if ($quote) { |
||
| 107 | ${is_null($value) ? 'key' : 'value'} .= $match[0]; |
||
| 108 | $consume($argument, $match[0]); |
||
| 109 | |||
| 110 | return; |
||
| 111 | } |
||
| 112 | |||
| 113 | if (!is_null($value)) { |
||
| 114 | throw new \ErrorException('Parse error on ' . substr($argument, strlen($match[1])), 15); |
||
| 115 | } |
||
| 116 | |||
| 117 | $key .= $match[1]; |
||
| 118 | $value = ''; |
||
| 119 | $consume($argument, $match[0]); |
||
| 120 | } |
||
| 121 | |||
| 122 | protected function parseArrayElement(&$argument, $match, $consume, &$quote, &$key, &$value) |
||
| 123 | { |
||
| 124 | switch ($match[2]) { |
||
| 125 | case '"': |
||
| 126 | case "'": |
||
| 127 | $this->parseArrayString($argument, $match, $consume, $quote, $key, $value); |
||
| 128 | break; |
||
| 129 | case ':': |
||
| 130 | case '=>': |
||
| 131 | $this->parseArrayAssign($argument, $match, $consume, $quote, $key, $value); |
||
| 132 | break; |
||
| 133 | case ',': |
||
| 134 | ${is_null($value) ? 'key' : 'value'} .= $match[0]; |
||
| 135 | $consume($argument, $match[0]); |
||
| 136 | break; |
||
| 137 | } |
||
| 138 | } |
||
| 139 | |||
| 140 | protected function parseArray($input, $subCodeHandler) |
||
| 141 | { |
||
| 142 | $output = array(); |
||
| 143 | $key = ''; |
||
| 144 | $value = null; |
||
| 145 | $addToOutput = $subCodeHandler->addToOutput($output, $key, $value); |
||
| 146 | $consume = $subCodeHandler->consume(); |
||
| 147 | foreach ($input as $argument) { |
||
| 148 | $argument = ltrim($argument, '$'); |
||
| 149 | $quote = null; |
||
| 150 | while (preg_match('/^(.*?)(=>|[\'",:])/', $argument, $match)) { |
||
| 151 | $this->parseArrayElement($argument, $match, $consume, $quote, $key, $value); |
||
| 152 | } |
||
| 153 | ${is_null($value) ? 'key' : 'value'} .= $argument; |
||
| 154 | $addToOutput(); |
||
| 155 | } |
||
| 156 | |||
| 157 | return 'array(' . implode(', ', $output) . ')'; |
||
| 158 | } |
||
| 159 | |||
| 160 | protected function parseEqual($sep, &$separators, &$result, $innerName, $subCodeHandler) |
||
| 161 | { |
||
| 162 | if (preg_match('/^[[:space:]]*$/', $innerName)) { |
||
| 163 | next($separators); |
||
| 164 | $handleCodeInbetween = $subCodeHandler->handleCodeInbetween($separators, $result); |
||
| 165 | |||
| 166 | return implode($handleCodeInbetween()); |
||
| 167 | } |
||
| 168 | |||
| 169 | $handleRecursion = $subCodeHandler->handleRecursion($result); |
||
| 170 | |||
| 171 | return $handleRecursion(array($sep, end($separators))); |
||
| 172 | } |
||
| 173 | |||
| 174 | protected function parseSeparator($sep, &$separators, &$result, &$varname, $subCodeHandler, $innerName) |
||
| 175 | { |
||
| 176 | $handleCodeInbetween = $subCodeHandler->handleCodeInbetween($separators, $result); |
||
| 177 | $var = '$__' . $this->name; |
||
| 178 | |||
| 179 | switch ($sep[0]) { |
||
| 180 | // translate the javascript's obj.attr into php's obj->attr or obj['attr'] |
||
| 181 | /* |
||
| 182 | case '.': |
||
| 183 | $result[] = sprintf("%s=is_array(%s)?%s['%s']:%s->%s", |
||
| 184 | $var, $varname, $varname, $innerName, $varname, $innerName |
||
| 185 | ); |
||
| 186 | $varname = $var; |
||
| 187 | break; |
||
| 188 | //*/ |
||
| 189 | |||
| 190 | // funcall |
||
| 191 | case '(': |
||
| 192 | $arguments = $handleCodeInbetween(); |
||
| 193 | $call = $varname . '(' . implode(', ', $arguments) . ')'; |
||
| 194 | $call = static::addDollarIfNeeded($call); |
||
| 195 | $varname = $var; |
||
| 196 | array_push($result, "{$var}={$call}"); |
||
| 197 | break; |
||
| 198 | |||
| 199 | case '[': |
||
| 200 | if (preg_match('/[a-zA-Z0-9\\\\_\\x7f-\\xff]$/', $varname)) { |
||
| 201 | $varname .= $sep[0] . $innerName; |
||
| 202 | break; |
||
| 203 | } |
||
| 204 | case '{': |
||
| 205 | $varname .= $this->parseArray($handleCodeInbetween(), $subCodeHandler); |
||
| 206 | break; |
||
| 207 | |||
| 208 | case '=': |
||
| 209 | $varname .= '=' . $this->parseEqual($sep, $separators, $result, $innerName, $subCodeHandler); |
||
| 210 | break; |
||
| 211 | |||
| 212 | default: |
||
| 213 | if (($innerName !== false && $innerName !== '') || $sep[0] !== ')') { |
||
| 214 | $varname .= $sep[0] . $innerName; |
||
| 215 | } |
||
| 216 | break; |
||
| 217 | } |
||
| 218 | } |
||
| 219 | |||
| 220 | protected function parseBetweenSeparators() |
||
| 221 | { |
||
| 222 | $separators = $this->separators; |
||
| 223 | |||
| 224 | $result = array(); |
||
| 225 | |||
| 226 | $varname = $this->getVarname($separators[0]); |
||
| 227 | |||
| 228 | $subCodeHandler = new SubCodeHandler($this, $this->input, $this->name); |
||
| 229 | $getMiddleString = $subCodeHandler->getMiddleString(); |
||
| 230 | $getNext = $subCodeHandler->getNext($separators); |
||
| 231 | |||
| 232 | // using next() ourselves so that we can advance the array pointer inside inner loops |
||
| 233 | while (($sep = current($separators)) && $sep[0] !== null) { |
||
| 234 | // $sep[0] - the separator string due to PREG_SPLIT_OFFSET_CAPTURE flag or null if end of string |
||
| 235 | // $sep[1] - the offset due to PREG_SPLIT_OFFSET_CAPTURE |
||
| 236 | |||
| 237 | $innerName = $getMiddleString($sep, $getNext(key($separators))); |
||
| 238 | |||
| 239 | $this->parseSeparator($sep, $separators, $result, $varname, $subCodeHandler, $innerName); |
||
| 240 | |||
| 241 | next($separators); |
||
| 242 | } |
||
| 243 | array_push($result, $varname); |
||
| 244 | |||
| 245 | return $result; |
||
| 246 | } |
||
| 247 | } |
||
| 248 |