Total Complexity | 126 |
Total Lines | 702 |
Duplicated Lines | 0 % |
Changes | 1 | ||
Bugs | 0 | Features | 0 |
Complex classes like GlobalNamespaceImportFixer 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 GlobalNamespaceImportFixer, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
42 | final class GlobalNamespaceImportFixer extends AbstractFixer implements ConfigurableFixerInterface, WhitespacesAwareFixerInterface |
||
43 | { |
||
44 | /** |
||
45 | * {@inheritdoc} |
||
46 | */ |
||
47 | public function getDefinition(): FixerDefinitionInterface |
||
48 | { |
||
49 | return new FixerDefinition( |
||
50 | 'Imports or fully qualifies global classes/functions/constants.', |
||
51 | [ |
||
52 | new CodeSample( |
||
53 | '<?php |
||
54 | |||
55 | namespace Foo; |
||
56 | |||
57 | $d = new \DateTimeImmutable(); |
||
58 | ' |
||
59 | ), |
||
60 | new CodeSample( |
||
61 | '<?php |
||
62 | |||
63 | namespace Foo; |
||
64 | |||
65 | if (\count($x)) { |
||
66 | /** @var \DateTimeImmutable $d */ |
||
67 | $d = new \DateTimeImmutable(); |
||
68 | $p = \M_PI; |
||
69 | } |
||
70 | ', |
||
71 | ['import_classes' => true, 'import_constants' => true, 'import_functions' => true] |
||
72 | ), |
||
73 | new CodeSample( |
||
74 | '<?php |
||
75 | |||
76 | namespace Foo; |
||
77 | |||
78 | use DateTimeImmutable; |
||
79 | use function count; |
||
80 | use const M_PI; |
||
81 | |||
82 | if (count($x)) { |
||
83 | /** @var DateTimeImmutable $d */ |
||
84 | $d = new DateTimeImmutable(); |
||
85 | $p = M_PI; |
||
86 | } |
||
87 | ', |
||
88 | ['import_classes' => false, 'import_constants' => false, 'import_functions' => false] |
||
89 | ), |
||
90 | ] |
||
91 | ); |
||
92 | } |
||
93 | |||
94 | /** |
||
95 | * {@inheritdoc} |
||
96 | * |
||
97 | * Must run before NoUnusedImportsFixer, OrderedImportsFixer. |
||
98 | * Must run after NativeConstantInvocationFixer, NativeFunctionInvocationFixer. |
||
99 | */ |
||
100 | public function getPriority(): int |
||
101 | { |
||
102 | return 0; |
||
103 | } |
||
104 | |||
105 | /** |
||
106 | * {@inheritdoc} |
||
107 | */ |
||
108 | public function isCandidate(Tokens $tokens): bool |
||
109 | { |
||
110 | return $tokens->isAnyTokenKindsFound([T_DOC_COMMENT, T_NS_SEPARATOR, T_USE]) |
||
111 | && $tokens->isTokenKindFound(T_NAMESPACE) |
||
112 | && 1 === $tokens->countTokenKind(T_NAMESPACE) |
||
113 | && $tokens->isMonolithicPhp(); |
||
114 | } |
||
115 | |||
116 | /** |
||
117 | * {@inheritdoc} |
||
118 | */ |
||
119 | protected function applyFix(\SplFileInfo $file, Tokens $tokens): void |
||
153 | } |
||
154 | } |
||
155 | |||
156 | protected function createConfigurationDefinition(): FixerConfigurationResolverInterface |
||
157 | { |
||
158 | return new FixerConfigurationResolver([ |
||
159 | (new FixerOptionBuilder('import_constants', 'Whether to import, not import or ignore global constants.')) |
||
160 | ->setDefault(null) |
||
161 | ->setAllowedValues([true, false, null]) |
||
162 | ->getOption(), |
||
163 | (new FixerOptionBuilder('import_functions', 'Whether to import, not import or ignore global functions.')) |
||
164 | ->setDefault(null) |
||
165 | ->setAllowedValues([true, false, null]) |
||
166 | ->getOption(), |
||
167 | (new FixerOptionBuilder('import_classes', 'Whether to import, not import or ignore global classes.')) |
||
168 | ->setDefault(true) |
||
169 | ->setAllowedValues([true, false, null]) |
||
170 | ->getOption(), |
||
171 | ]); |
||
172 | } |
||
173 | |||
174 | /** |
||
175 | * @param NamespaceUseAnalysis[] $useDeclarations |
||
176 | */ |
||
177 | private function importConstants(Tokens $tokens, array $useDeclarations): array |
||
178 | { |
||
179 | [$global, $other] = $this->filterUseDeclarations($useDeclarations, static function (NamespaceUseAnalysis $declaration) { |
||
180 | return $declaration->isConstant(); |
||
181 | }, true); |
||
182 | |||
183 | // find namespaced const declarations (`const FOO = 1`) |
||
184 | // and add them to the not importable names (already used) |
||
185 | for ($index = 0, $count = $tokens->count(); $index < $count; ++$index) { |
||
186 | $token = $tokens[$index]; |
||
187 | |||
188 | if ($token->isClassy()) { |
||
189 | $index = $tokens->getNextTokenOfKind($index, ['{']); |
||
190 | $index = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $index); |
||
191 | |||
192 | continue; |
||
193 | } |
||
194 | |||
195 | if (!$token->isGivenKind(T_CONST)) { |
||
196 | continue; |
||
197 | } |
||
198 | |||
199 | $index = $tokens->getNextMeaningfulToken($index); |
||
200 | $other[$tokens[$index]->getContent()] = true; |
||
201 | } |
||
202 | |||
203 | $analyzer = new TokensAnalyzer($tokens); |
||
204 | |||
205 | $indexes = []; |
||
206 | |||
207 | for ($index = $tokens->count() - 1; $index >= 0; --$index) { |
||
208 | $token = $tokens[$index]; |
||
209 | |||
210 | if (!$token->isGivenKind(T_STRING)) { |
||
211 | continue; |
||
212 | } |
||
213 | |||
214 | $name = $token->getContent(); |
||
215 | |||
216 | if (isset($other[$name])) { |
||
217 | continue; |
||
218 | } |
||
219 | |||
220 | if (!$analyzer->isConstantInvocation($index)) { |
||
221 | continue; |
||
222 | } |
||
223 | |||
224 | $nsSeparatorIndex = $tokens->getPrevMeaningfulToken($index); |
||
225 | if (!$tokens[$nsSeparatorIndex]->isGivenKind(T_NS_SEPARATOR)) { |
||
226 | if (!isset($global[$name])) { |
||
227 | // found an unqualified constant invocation |
||
228 | // add it to the not importable names (already used) |
||
229 | $other[$name] = true; |
||
230 | } |
||
231 | |||
232 | continue; |
||
233 | } |
||
234 | |||
235 | $prevIndex = $tokens->getPrevMeaningfulToken($nsSeparatorIndex); |
||
236 | if ($tokens[$prevIndex]->isGivenKind([CT::T_NAMESPACE_OPERATOR, T_STRING])) { |
||
237 | continue; |
||
238 | } |
||
239 | |||
240 | $indexes[] = $index; |
||
241 | } |
||
242 | |||
243 | return $this->prepareImports($tokens, $indexes, $global, $other, true); |
||
244 | } |
||
245 | |||
246 | /** |
||
247 | * @param NamespaceUseAnalysis[] $useDeclarations |
||
248 | */ |
||
249 | private function importFunctions(Tokens $tokens, array $useDeclarations): array |
||
250 | { |
||
251 | [$global, $other] = $this->filterUseDeclarations($useDeclarations, static function (NamespaceUseAnalysis $declaration) { |
||
252 | return $declaration->isFunction(); |
||
253 | }, false); |
||
254 | |||
255 | // find function declarations |
||
256 | // and add them to the not importable names (already used) |
||
257 | foreach ($this->findFunctionDeclarations($tokens, 0, $tokens->count() - 1) as $name) { |
||
258 | $other[strtolower($name)] = true; |
||
259 | } |
||
260 | |||
261 | $analyzer = new FunctionsAnalyzer(); |
||
262 | |||
263 | $indexes = []; |
||
264 | |||
265 | for ($index = $tokens->count() - 1; $index >= 0; --$index) { |
||
266 | $token = $tokens[$index]; |
||
267 | |||
268 | if (!$token->isGivenKind(T_STRING)) { |
||
269 | continue; |
||
270 | } |
||
271 | |||
272 | $name = strtolower($token->getContent()); |
||
273 | |||
274 | if (isset($other[$name])) { |
||
275 | continue; |
||
276 | } |
||
277 | |||
278 | if (!$analyzer->isGlobalFunctionCall($tokens, $index)) { |
||
279 | continue; |
||
280 | } |
||
281 | |||
282 | $nsSeparatorIndex = $tokens->getPrevMeaningfulToken($index); |
||
283 | if (!$tokens[$nsSeparatorIndex]->isGivenKind(T_NS_SEPARATOR)) { |
||
284 | if (!isset($global[$name])) { |
||
285 | $other[$name] = true; |
||
286 | } |
||
287 | |||
288 | continue; |
||
289 | } |
||
290 | |||
291 | $indexes[] = $index; |
||
292 | } |
||
293 | |||
294 | return $this->prepareImports($tokens, $indexes, $global, $other, false); |
||
295 | } |
||
296 | |||
297 | /** |
||
298 | * @param NamespaceUseAnalysis[] $useDeclarations |
||
299 | */ |
||
300 | private function importClasses(Tokens $tokens, array $useDeclarations): array |
||
408 | } |
||
409 | |||
410 | /** |
||
411 | * Removes the leading slash at the given indexes (when the name is not already used). |
||
412 | * |
||
413 | * @param int[] $indexes |
||
414 | * |
||
415 | * @return array array keys contain the names that must be imported |
||
416 | */ |
||
417 | private function prepareImports(Tokens $tokens, array $indexes, array $global, array $other, bool $caseSensitive): array |
||
418 | { |
||
419 | $imports = []; |
||
420 | |||
421 | foreach ($indexes as $index) { |
||
422 | $name = $tokens[$index]->getContent(); |
||
423 | $checkName = $caseSensitive ? $name : strtolower($name); |
||
424 | |||
425 | if (isset($other[$checkName])) { |
||
426 | continue; |
||
427 | } |
||
428 | |||
429 | if (!isset($global[$checkName])) { |
||
430 | $imports[$checkName] = $name; |
||
431 | } elseif (\is_string($global[$checkName])) { |
||
432 | $tokens[$index] = new Token([T_STRING, $global[$checkName]]); |
||
433 | } |
||
434 | |||
435 | $tokens->clearAt($tokens->getPrevMeaningfulToken($index)); |
||
436 | } |
||
437 | |||
438 | return $imports; |
||
439 | } |
||
440 | |||
441 | /** |
||
442 | * @param NamespaceUseAnalysis[] $useDeclarations |
||
443 | */ |
||
444 | private function insertImports(Tokens $tokens, array $imports, array $useDeclarations): void |
||
445 | { |
||
446 | if ($useDeclarations) { |
||
447 | $useDeclaration = end($useDeclarations); |
||
448 | $index = $useDeclaration->getEndIndex() + 1; |
||
449 | } else { |
||
450 | $namespace = (new NamespacesAnalyzer())->getDeclarations($tokens)[0]; |
||
451 | $index = $namespace->getEndIndex() + 1; |
||
452 | } |
||
453 | |||
454 | $lineEnding = $this->whitespacesConfig->getLineEnding(); |
||
455 | |||
456 | if (!$tokens[$index]->isWhitespace() || false === strpos($tokens[$index]->getContent(), "\n")) { |
||
457 | $tokens->insertAt($index, new Token([T_WHITESPACE, $lineEnding])); |
||
458 | } |
||
459 | |||
460 | foreach ($imports as $type => $typeImports) { |
||
461 | foreach ($typeImports as $name) { |
||
462 | $items = [ |
||
463 | new Token([T_WHITESPACE, $lineEnding]), |
||
464 | new Token([T_USE, 'use']), |
||
465 | new Token([T_WHITESPACE, ' ']), |
||
466 | ]; |
||
467 | |||
468 | if ('const' === $type) { |
||
469 | $items[] = new Token([CT::T_CONST_IMPORT, 'const']); |
||
470 | $items[] = new Token([T_WHITESPACE, ' ']); |
||
471 | } elseif ('function' === $type) { |
||
472 | $items[] = new Token([CT::T_FUNCTION_IMPORT, 'function']); |
||
473 | $items[] = new Token([T_WHITESPACE, ' ']); |
||
474 | } |
||
475 | |||
476 | $items[] = new Token([T_STRING, $name]); |
||
477 | $items[] = new Token(';'); |
||
478 | |||
479 | $tokens->insertAt($index, $items); |
||
480 | } |
||
481 | } |
||
482 | } |
||
483 | |||
484 | /** |
||
485 | * @param NamespaceUseAnalysis[] $useDeclarations |
||
486 | */ |
||
487 | private function fullyQualifyConstants(Tokens $tokens, array $useDeclarations): void |
||
523 | } |
||
524 | } |
||
525 | |||
526 | /** |
||
527 | * @param NamespaceUseAnalysis[] $useDeclarations |
||
528 | */ |
||
529 | private function fullyQualifyFunctions(Tokens $tokens, array $useDeclarations): void |
||
530 | { |
||
531 | if (!$tokens->isTokenKindFound(CT::T_FUNCTION_IMPORT)) { |
||
532 | return; |
||
533 | } |
||
534 | |||
535 | [$global] = $this->filterUseDeclarations($useDeclarations, static function (NamespaceUseAnalysis $declaration) { |
||
536 | return $declaration->isFunction() && !$declaration->isAliased(); |
||
537 | }, false); |
||
538 | |||
539 | if (!$global) { |
||
540 | return; |
||
541 | } |
||
542 | |||
543 | $analyzer = new FunctionsAnalyzer(); |
||
544 | |||
545 | for ($index = $tokens->count() - 1; $index >= 0; --$index) { |
||
546 | $token = $tokens[$index]; |
||
547 | |||
548 | if (!$token->isGivenKind(T_STRING)) { |
||
549 | continue; |
||
550 | } |
||
551 | |||
552 | if (!isset($global[strtolower($token->getContent())])) { |
||
553 | continue; |
||
554 | } |
||
555 | |||
556 | if ($tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind(T_NS_SEPARATOR)) { |
||
557 | continue; |
||
558 | } |
||
559 | |||
560 | if (!$analyzer->isGlobalFunctionCall($tokens, $index)) { |
||
561 | continue; |
||
562 | } |
||
563 | |||
564 | $tokens->insertAt($index, new Token([T_NS_SEPARATOR, '\\'])); |
||
565 | } |
||
566 | } |
||
567 | |||
568 | /** |
||
569 | * @param NamespaceUseAnalysis[] $useDeclarations |
||
570 | */ |
||
571 | private function fullyQualifyClasses(Tokens $tokens, array $useDeclarations): void |
||
572 | { |
||
573 | if (!$tokens->isTokenKindFound(T_USE)) { |
||
574 | return; |
||
575 | } |
||
576 | |||
577 | [$global] = $this->filterUseDeclarations($useDeclarations, static function (NamespaceUseAnalysis $declaration) { |
||
578 | return $declaration->isClass() && !$declaration->isAliased(); |
||
579 | }, false); |
||
580 | |||
581 | if (!$global) { |
||
582 | return; |
||
583 | } |
||
584 | |||
585 | $analyzer = new ClassyAnalyzer(); |
||
586 | |||
587 | for ($index = $tokens->count() - 1; $index >= 0; --$index) { |
||
588 | $token = $tokens[$index]; |
||
589 | |||
590 | if ($token->isGivenKind(T_DOC_COMMENT)) { |
||
591 | $doc = new DocBlock($token->getContent()); |
||
592 | |||
593 | $changed = $this->traverseDocBlockTypes($doc, static function (string $type) use ($global) { |
||
594 | if (!isset($global[strtolower($type)])) { |
||
595 | return $type; |
||
596 | } |
||
597 | |||
598 | return '\\'.$type; |
||
599 | }); |
||
600 | |||
601 | if ($changed) { |
||
602 | $tokens[$index] = new Token([T_DOC_COMMENT, $doc->getContent()]); |
||
603 | } |
||
604 | |||
605 | continue; |
||
606 | } |
||
607 | |||
608 | if (!$token->isGivenKind(T_STRING)) { |
||
609 | continue; |
||
610 | } |
||
611 | |||
612 | if (!isset($global[strtolower($token->getContent())])) { |
||
613 | continue; |
||
614 | } |
||
615 | |||
616 | if ($tokens[$tokens->getPrevMeaningfulToken($index)]->isGivenKind(T_NS_SEPARATOR)) { |
||
617 | continue; |
||
618 | } |
||
619 | |||
620 | if (!$analyzer->isClassyInvocation($tokens, $index)) { |
||
621 | continue; |
||
622 | } |
||
623 | |||
624 | $tokens->insertAt($index, new Token([T_NS_SEPARATOR, '\\'])); |
||
625 | } |
||
626 | } |
||
627 | |||
628 | /** |
||
629 | * @param NamespaceUseAnalysis[] $declarations |
||
630 | */ |
||
631 | private function filterUseDeclarations(array $declarations, callable $callback, bool $caseSensitive): array |
||
632 | { |
||
633 | $global = []; |
||
634 | $other = []; |
||
635 | |||
636 | foreach ($declarations as $declaration) { |
||
637 | if (!$callback($declaration)) { |
||
638 | continue; |
||
639 | } |
||
640 | |||
641 | $fullName = ltrim($declaration->getFullName(), '\\'); |
||
642 | |||
643 | if (false !== strpos($fullName, '\\')) { |
||
644 | $name = $caseSensitive ? $declaration->getShortName() : strtolower($declaration->getShortName()); |
||
645 | $other[$name] = true; |
||
646 | |||
647 | continue; |
||
648 | } |
||
649 | |||
650 | $checkName = $caseSensitive ? $fullName : strtolower($fullName); |
||
651 | $alias = $declaration->getShortName(); |
||
652 | $global[$checkName] = $alias === $fullName ? true : $alias; |
||
653 | } |
||
654 | |||
655 | return [$global, $other]; |
||
656 | } |
||
657 | |||
658 | private function findFunctionDeclarations(Tokens $tokens, int $start, int $end): iterable |
||
659 | { |
||
660 | for ($index = $start; $index <= $end; ++$index) { |
||
661 | $token = $tokens[$index]; |
||
662 | |||
663 | if ($token->isClassy()) { |
||
664 | $classStart = $tokens->getNextTokenOfKind($index, ['{']); |
||
665 | $classEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $classStart); |
||
666 | |||
667 | for ($index = $classStart; $index <= $classEnd; ++$index) { |
||
668 | if (!$tokens[$index]->isGivenKind(T_FUNCTION)) { |
||
669 | continue; |
||
670 | } |
||
671 | |||
672 | $methodStart = $tokens->getNextTokenOfKind($index, ['{', ';']); |
||
673 | |||
674 | if ($tokens[$methodStart]->equals(';')) { |
||
675 | $index = $methodStart; |
||
676 | |||
677 | continue; |
||
678 | } |
||
679 | |||
680 | $methodEnd = $tokens->findBlockEnd(Tokens::BLOCK_TYPE_CURLY_BRACE, $methodStart); |
||
681 | |||
682 | foreach ($this->findFunctionDeclarations($tokens, $methodStart, $methodEnd) as $function) { |
||
683 | yield $function; |
||
684 | } |
||
685 | |||
686 | $index = $methodEnd; |
||
687 | } |
||
688 | |||
689 | continue; |
||
690 | } |
||
691 | |||
692 | if (!$token->isGivenKind(T_FUNCTION)) { |
||
693 | continue; |
||
694 | } |
||
695 | |||
696 | $index = $tokens->getNextMeaningfulToken($index); |
||
697 | |||
698 | if ($tokens[$index]->isGivenKind(CT::T_RETURN_REF)) { |
||
699 | $index = $tokens->getNextMeaningfulToken($index); |
||
700 | } |
||
701 | |||
702 | if ($tokens[$index]->isGivenKind(T_STRING)) { |
||
703 | yield $tokens[$index]->getContent(); |
||
704 | } |
||
705 | } |
||
706 | } |
||
707 | |||
708 | private function traverseDocBlockTypes(DocBlock $doc, callable $callback): bool |
||
744 | } |
||
745 | } |
||
746 |
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.