Complex classes like NodeExpressionResolver 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 NodeExpressionResolver, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
31 | class NodeExpressionResolver |
||
32 | { |
||
33 | |||
34 | /** |
||
35 | * List of exception for constant fetch |
||
36 | * |
||
37 | * @var array |
||
38 | */ |
||
39 | private static $notConstants = [ |
||
40 | 'true' => true, |
||
41 | 'false' => true, |
||
42 | 'null' => true, |
||
43 | ]; |
||
44 | |||
45 | /** |
||
46 | * Name of the constant (if present) |
||
47 | * |
||
48 | * @var ?string |
||
49 | */ |
||
50 | private $constantName; |
||
51 | |||
52 | /** |
||
53 | * Current reflection context for parsing |
||
54 | * |
||
55 | * @var mixed|ReflectionClass |
||
56 | */ |
||
57 | private $context; |
||
58 | |||
59 | /** |
||
60 | * Flag if expression is constant |
||
61 | * |
||
62 | * @var bool |
||
63 | */ |
||
64 | private $isConstant = false; |
||
65 | |||
66 | /** |
||
67 | * Node resolving level, 1 = top-level |
||
68 | * |
||
69 | * @var int |
||
70 | */ |
||
71 | private $nodeLevel = 0; |
||
72 | 76 | ||
73 | /** |
||
74 | 76 | * @var mixed Value of expression/constant |
|
75 | 76 | */ |
|
76 | private $value; |
||
77 | 21 | ||
78 | public function __construct($context) |
||
79 | 21 | { |
|
80 | $this->context = $context; |
||
81 | } |
||
82 | 51 | ||
83 | public function getConstantName(): ?string |
||
84 | 51 | { |
|
85 | return $this->constantName; |
||
86 | } |
||
87 | 21 | ||
88 | public function getValue() |
||
89 | 21 | { |
|
90 | return $this->value; |
||
91 | } |
||
92 | |||
93 | public function isConstant(): bool |
||
94 | { |
||
95 | 53 | return $this->isConstant; |
|
96 | } |
||
97 | |||
98 | 53 | public function process(Node $node): void |
|
99 | 3 | { |
|
100 | // Unwrap "expr;" statements. |
||
101 | if ($node instanceof Expression) { |
||
102 | 53 | $node = $node->expr; |
|
103 | 53 | } |
|
104 | 53 | ||
105 | 53 | $this->nodeLevel = 0; |
|
106 | 51 | $this->isConstant = false; |
|
107 | $this->constantName = null; |
||
108 | $this->value = $this->resolve($node); |
||
109 | } |
||
110 | |||
111 | /** |
||
112 | * Resolves node into valid value |
||
113 | * |
||
114 | * @param Node $node |
||
115 | 53 | * |
|
116 | * @return mixed |
||
117 | 53 | */ |
|
118 | protected function resolve(Node $node) |
||
119 | 53 | { |
|
120 | $value = null; |
||
121 | 53 | try { |
|
122 | 53 | ++$this->nodeLevel; |
|
123 | 53 | ||
124 | $methodName = $this->getDispatchMethodFor($node); |
||
125 | 53 | if (method_exists($this, $methodName)) { |
|
126 | 53 | $value = $this->$methodName($node); |
|
127 | } |
||
128 | } finally { |
||
129 | 53 | --$this->nodeLevel; |
|
130 | } |
||
131 | |||
132 | 9 | return $value; |
|
133 | } |
||
134 | 9 | ||
135 | protected function resolveScalarDNumber(DNumber $node): float |
||
136 | { |
||
137 | 28 | return $node->value; |
|
138 | } |
||
139 | 28 | ||
140 | protected function resolveScalarLNumber(LNumber $node): int |
||
141 | { |
||
142 | 25 | return $node->value; |
|
143 | } |
||
144 | 25 | ||
145 | protected function resolveScalarString(String_ $node): string |
||
146 | { |
||
147 | return $node->value; |
||
148 | } |
||
149 | |||
150 | protected function resolveScalarMagicConstMethod(): string |
||
151 | { |
||
152 | if ($this->context instanceof ReflectionMethod) { |
||
153 | $fullName = $this->context->getDeclaringClass()->name . '::' . $this->context->getShortName(); |
||
154 | |||
155 | return $fullName; |
||
156 | } |
||
157 | |||
158 | 1 | return ''; |
|
159 | } |
||
160 | 1 | ||
161 | 1 | protected function resolveScalarMagicConstFunction(): string |
|
162 | { |
||
163 | if ($this->context instanceof ReflectionFunctionAbstract) { |
||
164 | return $this->context->getName(); |
||
165 | } |
||
166 | |||
167 | 30 | return ''; |
|
168 | } |
||
169 | 30 | ||
170 | 27 | protected function resolveScalarMagicConstNamespace(): string |
|
171 | { |
||
172 | if (method_exists($this->context, 'getNamespaceName')) { |
||
173 | 3 | return $this->context->getNamespaceName(); |
|
174 | 3 | } |
|
175 | |||
176 | if ($this->context instanceof ReflectionFileNamespace) { |
||
177 | return $this->context->getName(); |
||
178 | } |
||
179 | |||
180 | 11 | return ''; |
|
181 | } |
||
182 | 11 | ||
183 | 5 | protected function resolveScalarMagicConstClass(): string |
|
184 | { |
||
185 | 6 | if ($this->context instanceof \ReflectionClass) { |
|
186 | return $this->context->name; |
||
187 | } |
||
188 | if (method_exists($this->context, 'getDeclaringClass')) { |
||
189 | $declaringClass = $this->context->getDeclaringClass(); |
||
190 | if ($declaringClass instanceof \ReflectionClass) { |
||
191 | return $declaringClass->name; |
||
192 | 6 | } |
|
193 | } |
||
194 | |||
195 | 4 | return ''; |
|
196 | } |
||
197 | 4 | ||
198 | 4 | protected function resolveScalarMagicConstDir(): string |
|
199 | { |
||
200 | if (method_exists($this->context, 'getFileName')) { |
||
201 | return dirname($this->context->getFileName()); |
||
202 | } |
||
203 | |||
204 | 7 | return ''; |
|
205 | } |
||
206 | 7 | ||
207 | 7 | protected function resolveScalarMagicConstFile(): string |
|
208 | { |
||
209 | if (method_exists($this->context, 'getFileName')) { |
||
210 | return $this->context->getFileName(); |
||
211 | } |
||
212 | |||
213 | 7 | return ''; |
|
214 | } |
||
215 | 7 | ||
216 | protected function resolveScalarMagicConstLine(Line $node): int |
||
217 | { |
||
218 | 2 | return $node->hasAttribute('startLine') ? $node->getAttribute('startLine') : 0; |
|
219 | } |
||
220 | 2 | ||
221 | 2 | protected function resolveScalarMagicConstTrait(): string |
|
222 | { |
||
223 | if ($this->context instanceof \ReflectionClass && $this->context->isTrait()) { |
||
224 | return $this->context->name; |
||
225 | } |
||
226 | |||
227 | 28 | return ''; |
|
228 | } |
||
229 | 28 | ||
230 | 28 | protected function resolveExprConstFetch(Expr\ConstFetch $node) |
|
231 | { |
||
232 | 28 | $constantValue = null; |
|
233 | 28 | $isResolved = false; |
|
234 | |||
235 | 28 | $isFQNConstant = $node->name instanceof Node\Name\FullyQualified; |
|
236 | 24 | $constantName = $node->name->toString(); |
|
237 | 24 | ||
238 | 24 | if (!$isFQNConstant && method_exists($this->context, 'getFileName')) { |
|
239 | 24 | $fileName = $this->context->getFileName(); |
|
240 | 24 | $namespaceName = $this->resolveScalarMagicConstNamespace(); |
|
241 | 13 | $fileNamespace = new ReflectionFileNamespace($fileName, $namespaceName); |
|
242 | 13 | if ($fileNamespace->hasConstant($constantName)) { |
|
243 | 13 | $constantValue = $fileNamespace->getConstant($constantName); |
|
244 | $constantName = $fileNamespace->getName() . '\\' . $constantName; |
||
245 | $isResolved = true; |
||
246 | } |
||
247 | } |
||
248 | 28 | ||
249 | 26 | if (!$isResolved && defined($constantName)) { |
|
250 | $constantValue = constant($constantName); |
||
251 | } |
||
252 | 28 | ||
253 | 16 | if ($this->nodeLevel === 1 && !isset(self::$notConstants[$constantName])) { |
|
254 | 16 | $this->isConstant = true; |
|
255 | $this->constantName = $constantName; |
||
256 | } |
||
257 | 28 | ||
258 | return $constantValue; |
||
259 | } |
||
260 | 15 | ||
261 | protected function resolveExprClassConstFetch(Expr\ClassConstFetch $node) |
||
262 | 15 | { |
|
263 | 15 | $classToReflect = $node->class; |
|
264 | 3 | if (!($classToReflect instanceof Node\Name)) { |
|
265 | 3 | $classToReflect = $this->resolve($classToReflect) ?: $classToReflect; |
|
266 | 2 | if (!is_string($classToReflect)) { |
|
267 | 2 | $reason = 'Unable'; |
|
268 | 1 | if ($classToReflect instanceof Expr) { |
|
269 | 1 | $methodName = $this->getDispatchMethodFor($classToReflect); |
|
270 | $reason = "Method " . __CLASS__ . "::{$methodName}() not found trying"; |
||
271 | 2 | } |
|
272 | throw new ReflectionException("$reason to resolve class constant."); |
||
273 | } |
||
274 | // Strings evaluated as class names are always treated as fully |
||
275 | 1 | // qualified. |
|
276 | $classToReflect = new Node\Name\FullyQualified(ltrim($classToReflect, '\\')); |
||
277 | 13 | } |
|
278 | 13 | $refClass = $this->fetchReflectionClass($classToReflect); |
|
279 | $constantName = ($node->name instanceof Expr\Error) ? '' : $node->name->toString(); |
||
280 | |||
281 | 13 | // special handling of ::class constants |
|
282 | 3 | if ('class' === $constantName) { |
|
283 | return $refClass->getName(); |
||
284 | } |
||
285 | 11 | ||
286 | 11 | $this->isConstant = true; |
|
287 | $this->constantName = $classToReflect . '::' . $constantName; |
||
288 | 11 | ||
289 | return $refClass->getConstant($constantName); |
||
290 | } |
||
291 | 9 | ||
292 | protected function resolveExprArray(Expr\Array_ $node): array |
||
293 | 9 | { |
|
294 | 9 | $result = []; |
|
295 | 8 | foreach ($node->items as $itemIndex => $arrayItem) { |
|
296 | 8 | $itemValue = $this->resolve($arrayItem->value); |
|
297 | 8 | $itemKey = isset($arrayItem->key) ? $this->resolve($arrayItem->key) : $itemIndex; |
|
298 | $result[$itemKey] = $itemValue; |
||
299 | } |
||
300 | 9 | ||
301 | return $result; |
||
302 | } |
||
303 | 3 | ||
304 | protected function resolveExprBinaryOpPlus(Expr\BinaryOp\Plus $node) |
||
305 | 3 | { |
|
306 | return $this->resolve($node->left) + $this->resolve($node->right); |
||
307 | } |
||
308 | 1 | ||
309 | protected function resolveExprBinaryOpMinus(Expr\BinaryOp\Minus $node) |
||
310 | 1 | { |
|
311 | return $this->resolve($node->left) - $this->resolve($node->right); |
||
312 | } |
||
313 | 2 | ||
314 | protected function resolveExprBinaryOpMul(Expr\BinaryOp\Mul $node) |
||
315 | 2 | { |
|
316 | return $this->resolve($node->left) * $this->resolve($node->right); |
||
317 | } |
||
318 | 1 | ||
319 | protected function resolveExprBinaryOpPow(Expr\BinaryOp\Pow $node) |
||
320 | 1 | { |
|
321 | return $this->resolve($node->left) ** $this->resolve($node->right); |
||
322 | } |
||
323 | 1 | ||
324 | protected function resolveExprBinaryOpDiv(Expr\BinaryOp\Div $node) |
||
325 | 1 | { |
|
326 | return $this->resolve($node->left) / $this->resolve($node->right); |
||
327 | } |
||
328 | 1 | ||
329 | protected function resolveExprBinaryOpMod(Expr\BinaryOp\Mod $node): int |
||
330 | 1 | { |
|
331 | return $this->resolve($node->left) % $this->resolve($node->right); |
||
332 | } |
||
333 | 1 | ||
334 | protected function resolveExprBooleanNot(Expr\BooleanNot $node): bool |
||
335 | 1 | { |
|
336 | return !$this->resolve($node->expr); |
||
337 | } |
||
338 | 1 | ||
339 | protected function resolveExprBitwiseNot(Expr\BitwiseNot $node) |
||
340 | 1 | { |
|
341 | return ~$this->resolve($node->expr); |
||
342 | } |
||
343 | 1 | ||
344 | protected function resolveExprBinaryOpBitwiseOr(Expr\BinaryOp\BitwiseOr $node): int |
||
345 | 1 | { |
|
346 | return $this->resolve($node->left) | $this->resolve($node->right); |
||
347 | } |
||
348 | 1 | ||
349 | protected function resolveExprBinaryOpBitwiseAnd(Expr\BinaryOp\BitwiseAnd $node): int |
||
350 | 1 | { |
|
351 | return $this->resolve($node->left) & $this->resolve($node->right); |
||
352 | } |
||
353 | 1 | ||
354 | protected function resolveExprBinaryOpBitwiseXor(Expr\BinaryOp\BitwiseXor $node): int |
||
355 | 1 | { |
|
356 | return $this->resolve($node->left) ^ $this->resolve($node->right); |
||
357 | } |
||
358 | 1 | ||
359 | protected function resolveExprBinaryOpShiftLeft(Expr\BinaryOp\ShiftLeft $node): int |
||
360 | 1 | { |
|
361 | return $this->resolve($node->left) << $this->resolve($node->right); |
||
362 | } |
||
363 | 1 | ||
364 | protected function resolveExprBinaryOpShiftRight(Expr\BinaryOp\ShiftRight $node): int |
||
365 | 1 | { |
|
366 | return $this->resolve($node->left) >> $this->resolve($node->right); |
||
367 | } |
||
368 | 3 | ||
369 | protected function resolveExprBinaryOpConcat(Expr\BinaryOp\Concat $node): string |
||
370 | 3 | { |
|
371 | return $this->resolve($node->left) . $this->resolve($node->right); |
||
372 | } |
||
373 | 1 | ||
374 | protected function resolveExprTernary(Expr\Ternary $node) |
||
375 | 1 | { |
|
376 | if (isset($node->if)) { |
||
377 | // Full syntax $a ? $b : $c; |
||
378 | |||
379 | return $this->resolve($node->cond) ? $this->resolve($node->if) : $this->resolve($node->else); |
||
380 | } |
||
381 | |||
382 | 1 | return $this->resolve($node->cond) ?: $this->resolve($node->else); |
|
383 | } |
||
384 | |||
385 | protected function resolveExprBinaryOpSmallerOrEqual(Expr\BinaryOp\SmallerOrEqual $node): bool |
||
389 | |||
390 | protected function resolveExprBinaryOpGreaterOrEqual(Expr\BinaryOp\GreaterOrEqual $node): bool |
||
394 | |||
395 | protected function resolveExprBinaryOpEqual(Expr\BinaryOp\Equal $node): bool |
||
400 | |||
401 | 1 | protected function resolveExprBinaryOpNotEqual(Expr\BinaryOp\NotEqual $node): bool |
|
406 | 1 | ||
407 | protected function resolveExprBinaryOpSmaller(Expr\BinaryOp\Smaller $node): bool |
||
408 | 1 | { |
|
409 | return $this->resolve($node->left) < $this->resolve($node->right); |
||
410 | } |
||
411 | 2 | ||
412 | protected function resolveExprBinaryOpGreater(Expr\BinaryOp\Greater $node): bool |
||
413 | 2 | { |
|
414 | return $this->resolve($node->left) > $this->resolve($node->right); |
||
415 | } |
||
416 | 1 | ||
417 | protected function resolveExprBinaryOpIdentical(Expr\BinaryOp\Identical $node): bool |
||
418 | 1 | { |
|
419 | return $this->resolve($node->left) === $this->resolve($node->right); |
||
420 | } |
||
421 | 1 | ||
422 | protected function resolveExprBinaryOpNotIdentical(Expr\BinaryOp\NotIdentical $node): bool |
||
423 | 1 | { |
|
424 | return $this->resolve($node->left) !== $this->resolve($node->right); |
||
425 | } |
||
426 | 1 | ||
427 | protected function resolveExprBinaryOpBooleanAnd(Expr\BinaryOp\BooleanAnd $node): bool |
||
428 | 1 | { |
|
429 | return $this->resolve($node->left) && $this->resolve($node->right); |
||
430 | } |
||
431 | 1 | ||
432 | protected function resolveExprBinaryOpLogicalAnd(Expr\BinaryOp\LogicalAnd $node): bool |
||
433 | 1 | { |
|
434 | return $this->resolve($node->left) and $this->resolve($node->right); |
||
435 | } |
||
436 | 1 | ||
437 | protected function resolveExprBinaryOpBooleanOr(Expr\BinaryOp\BooleanOr $node): bool |
||
438 | 1 | { |
|
439 | return $this->resolve($node->left) || $this->resolve($node->right); |
||
440 | } |
||
441 | 1 | ||
442 | protected function resolveExprBinaryOpLogicalOr(Expr\BinaryOp\LogicalOr $node): bool |
||
443 | 1 | { |
|
444 | return $this->resolve($node->left) or $this->resolve($node->right); |
||
445 | } |
||
446 | 1 | ||
447 | protected function resolveExprBinaryOpLogicalXor(Expr\BinaryOp\LogicalXor $node): bool |
||
448 | 1 | { |
|
449 | return $this->resolve($node->left) xor $this->resolve($node->right); |
||
451 | 53 | ||
452 | private function getDispatchMethodFor(Node $node): string |
||
458 | |||
459 | /** |
||
460 | * Utility method to fetch reflection class instance by name |
||
461 | * |
||
462 | * Supports: |
||
463 | * 'self' keyword |
||
464 | * 'parent' keyword |
||
465 | * not-FQN class names |
||
466 | * |
||
467 | * @param Node\Name $node Class name node |
||
468 | * |
||
469 | * @return bool|\ReflectionClass |
||
470 | * |
||
471 | 13 | * @throws ReflectionException |
|
472 | */ |
||
473 | 13 | private function fetchReflectionClass(Node\Name $node) |
|
524 | } |
||
525 |