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