We could not synchronize checks via GitHub's checks API since Scrutinizer's GitHub App is not installed for this repository.
Complex classes like AnnotationParser 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 AnnotationParser, and based on these observations, apply Extract Interface, too.
| 1 | <?php |
||
| 14 | class AnnotationParser implements PreParserInterface |
||
| 15 | { |
||
| 16 | public const CLASSESMAP_CONTAINER_PARAMETER = 'overblog_graphql_types.classes_map'; |
||
| 17 | |||
| 18 | private static $annotationReader = null; |
||
| 19 | private static $classesMap = []; |
||
| 20 | private static $providers = []; |
||
| 21 | private static $doctrineMapping = []; |
||
| 22 | |||
| 23 | /** |
||
| 24 | * {@inheritdoc} |
||
| 25 | * |
||
| 26 | * @throws \ReflectionException |
||
| 27 | * @throws InvalidArgumentException |
||
| 28 | */ |
||
| 29 | public static function preParse(\SplFileInfo $file, ContainerBuilder $container, array $configs = []): void |
||
| 30 | 13 | { |
|
| 31 | self::proccessFile($file, $container, $configs, true); |
||
| 32 | 13 | } |
|
| 33 | |||
| 34 | 13 | /** |
|
| 35 | * {@inheritdoc} |
||
| 36 | 13 | * |
|
| 37 | 13 | * @throws \ReflectionException |
|
| 38 | 13 | * @throws InvalidArgumentException |
|
| 39 | */ |
||
| 40 | public static function parse(\SplFileInfo $file, ContainerBuilder $container, array $configs = []): array |
||
| 41 | { |
||
| 42 | return self::proccessFile($file, $container, $configs); |
||
| 43 | 13 | } |
|
| 44 | |||
| 45 | 13 | /** |
|
| 46 | * Clear the Annotation parser. |
||
| 47 | 13 | */ |
|
| 48 | public static function clear(): void |
||
| 49 | 13 | { |
|
| 50 | 13 | self::$classesMap = []; |
|
| 51 | 8 | self::$providers = []; |
|
| 52 | self::$annotationReader = null; |
||
| 53 | } |
||
| 54 | 13 | ||
| 55 | 13 | /** |
|
| 56 | 13 | * Process a file. |
|
| 57 | 3 | * |
|
| 58 | * @param \SplFileInfo $file |
||
| 59 | * @param ContainerBuilder $container |
||
| 60 | 13 | * @param bool $resolveClassMap |
|
| 61 | * |
||
| 62 | 13 | * @throws \ReflectionException |
|
| 63 | 13 | * @throws InvalidArgumentException |
|
| 64 | 13 | */ |
|
| 65 | 13 | public static function proccessFile(\SplFileInfo $file, ContainerBuilder $container, array $configs, bool $resolveClassMap = false): array |
|
| 66 | 7 | { |
|
| 67 | 7 | self::$doctrineMapping = $configs['doctrine']['types_mapping']; |
|
| 68 | 9 | ||
| 69 | 1 | $rootQueryType = $configs['definitions']['schema']['default']['query'] ?? false; |
|
| 70 | 1 | $rootMutationType = $configs['definitions']['schema']['default']['mutation'] ?? false; |
|
| 71 | 9 | ||
| 72 | 2 | $container->addResource(new FileResource($file->getRealPath())); |
|
| 73 | 2 | ||
| 74 | 8 | if (!$resolveClassMap) { |
|
| 75 | 1 | $container->setParameter(self::CLASSESMAP_CONTAINER_PARAMETER, self::$classesMap); |
|
| 76 | 1 | } |
|
| 77 | 8 | ||
| 78 | 1 | try { |
|
| 79 | 1 | $fileContent = \file_get_contents($file->getRealPath()); |
|
| 80 | 8 | ||
| 81 | 1 | $shortClassName = \substr($file->getFilename(), 0, -4); |
|
| 82 | 1 | if (\preg_match('#namespace (.+);#', $fileContent, $namespace)) { |
|
| 83 | $className = $namespace[1].'\\'.$shortClassName; |
||
| 84 | $namespace = $namespace[1]; |
||
| 85 | 13 | } else { |
|
| 86 | 13 | $className = $shortClassName; |
|
| 87 | } |
||
| 88 | |||
| 89 | $reflexionEntity = new \ReflectionClass($className); |
||
| 90 | 13 | ||
| 91 | $classAnnotations = self::getAnnotationReader()->getClassAnnotations($reflexionEntity); |
||
| 92 | |||
| 93 | $properties = []; |
||
| 94 | foreach ($reflexionEntity->getProperties() as $property) { |
||
| 95 | $properties[$property->getName()] = ['property' => $property, 'annotations' => self::getAnnotationReader()->getPropertyAnnotations($property)]; |
||
| 96 | } |
||
| 97 | |||
| 98 | $methods = []; |
||
| 99 | foreach ($reflexionEntity->getMethods() as $method) { |
||
| 100 | $methods[$method->getName()] = ['method' => $method, 'annotations' => self::getAnnotationReader()->getMethodAnnotations($method)]; |
||
|
|
|||
| 101 | 13 | } |
|
| 102 | |||
| 103 | 13 | $gqlTypes = []; |
|
| 104 | 1 | ||
| 105 | 1 | foreach ($classAnnotations as $classAnnotation) { |
|
| 106 | $gqlConfiguration = $gqlType = $gqlName = false; |
||
| 107 | |||
| 108 | switch (true) { |
||
| 109 | 1 | case $classAnnotation instanceof GQL\Type: |
|
| 110 | 1 | $gqlType = 'type'; |
|
| 111 | $gqlName = $classAnnotation->name ?: $shortClassName; |
||
| 112 | if (!$resolveClassMap) { |
||
| 113 | 13 | $isRootQuery = ($rootQueryType && $gqlName === $rootQueryType); |
|
| 114 | $isRootMutation = ($rootMutationType && $gqlName === $rootMutationType); |
||
| 115 | $currentValue = ($isRootQuery || $isRootMutation) ? \sprintf("service('%s')", self::formatNamespaceForExpression($className)) : 'value'; |
||
| 116 | |||
| 117 | $gqlConfiguration = self::getGraphqlType($classAnnotation, $classAnnotations, $properties, $methods, $namespace, $currentValue); |
||
| 118 | |||
| 119 | if ($isRootQuery || $isRootMutation) { |
||
| 120 | foreach (self::$providers as $className => $providerMethods) { |
||
| 121 | $gqlConfiguration['config']['fields'] += self::getGraphqlFieldsFromProvider($className, $providerMethods, $isRootMutation); |
||
| 122 | } |
||
| 123 | } |
||
| 124 | } |
||
| 125 | break; |
||
| 126 | case $classAnnotation instanceof GQL\Input: |
||
| 127 | 7 | $gqlType = 'input'; |
|
| 128 | $gqlName = $classAnnotation->name ?: self::suffixName($shortClassName, 'Input'); |
||
| 129 | 7 | if (!$resolveClassMap) { |
|
| 130 | 7 | $gqlConfiguration = self::getGraphqlInput($classAnnotation, $classAnnotations, $properties, $namespace); |
|
| 131 | } |
||
| 132 | 7 | break; |
|
| 133 | 7 | case $classAnnotation instanceof GQL\Scalar: |
|
| 134 | $gqlType = 'scalar'; |
||
| 135 | 7 | if (!$resolveClassMap) { |
|
| 136 | $gqlConfiguration = self::getGraphqlScalar($className, $classAnnotation, $classAnnotations); |
||
| 137 | } |
||
| 138 | break; |
||
| 139 | 7 | case $classAnnotation instanceof GQL\Enum: |
|
| 140 | $gqlType = 'enum'; |
||
| 141 | 7 | if (!$resolveClassMap) { |
|
| 142 | 7 | $gqlConfiguration = self::getGraphqlEnum($classAnnotation, $classAnnotations, $reflexionEntity->getConstants()); |
|
| 143 | 1 | } |
|
| 144 | break; |
||
| 145 | case $classAnnotation instanceof GQL\Union: |
||
| 146 | 7 | $gqlType = 'union'; |
|
| 147 | 7 | if (!$resolveClassMap) { |
|
| 148 | 1 | $gqlConfiguration = self::getGraphqlUnion($classAnnotation, $classAnnotations); |
|
| 149 | } |
||
| 150 | break; |
||
| 151 | 7 | case $classAnnotation instanceof GQL\TypeInterface: |
|
| 152 | 7 | $gqlType = 'interface'; |
|
| 153 | if (!$resolveClassMap) { |
||
| 154 | $gqlConfiguration = self::getGraphqlInterface($classAnnotation, $classAnnotations, $properties, $methods, $namespace); |
||
| 155 | } |
||
| 156 | 7 | break; |
|
| 157 | case $classAnnotation instanceof GQL\Provider: |
||
| 158 | if ($resolveClassMap) { |
||
| 159 | self::$providers[$className] = $methods; |
||
| 160 | } |
||
| 161 | break; |
||
| 162 | default: |
||
| 163 | continue; |
||
| 164 | } |
||
| 165 | |||
| 166 | if ($gqlType) { |
||
| 167 | if (!$gqlName) { |
||
| 168 | 1 | $gqlName = $classAnnotation->name ?: $shortClassName; |
|
| 169 | } |
||
| 170 | 1 | ||
| 171 | 1 | if ($resolveClassMap) { |
|
| 172 | if (isset(self::$classesMap[$gqlName])) { |
||
| 173 | 1 | throw new InvalidArgumentException(\sprintf('The GraphQL type "%s" has already been registered in class "%s"', $gqlName, self::$classesMap[$gqlName]['class'])); |
|
| 174 | 1 | } |
|
| 175 | self::$classesMap[$gqlName] = ['type' => $gqlType, 'class' => $className]; |
||
| 176 | 1 | } else { |
|
| 177 | $gqlTypes += [$gqlName => $gqlConfiguration]; |
||
| 178 | } |
||
| 179 | } |
||
| 180 | 1 | } |
|
| 181 | 1 | ||
| 182 | return $resolveClassMap ? self::$classesMap : $gqlTypes; |
||
| 183 | 1 | } catch (\InvalidArgumentException $e) { |
|
| 184 | throw new InvalidArgumentException(\sprintf('Failed to parse GraphQL annotations from file "%s".', $file), $e->getCode(), $e); |
||
| 185 | } |
||
| 186 | } |
||
| 187 | |||
| 188 | /** |
||
| 189 | * Retrieve annotation reader. |
||
| 190 | * |
||
| 191 | * @return AnnotationReader |
||
| 192 | */ |
||
| 193 | private static function getAnnotationReader() |
||
| 207 | |||
| 208 | 1 | /** |
|
| 209 | * Create a GraphQL Type configuration from annotations on class, properties and methods. |
||
| 210 | * |
||
| 211 | * @param GQL\Type $typeAnnotation |
||
| 212 | * @param array $classAnnotations |
||
| 213 | * @param array $properties |
||
| 214 | * @param array $methods |
||
| 215 | * @param string $namespace |
||
| 216 | * @param string $currentValue |
||
| 217 | * |
||
| 218 | * @return array |
||
| 219 | */ |
||
| 220 | private static function getGraphqlType(GQL\Type $typeAnnotation, array $classAnnotations, array $properties, array $methods, string $namespace, string $currentValue) |
||
| 250 | |||
| 251 | 1 | /** |
|
| 252 | * Create a GraphQL Interface type configuration from annotations on properties. |
||
| 253 | 1 | * |
|
| 254 | 1 | * @param string $shortClassName |
|
| 255 | * @param GQL\Interface $interfaceAnnotation |
||
| 256 | 1 | * @param array $properties |
|
| 257 | * @param array $methods |
||
| 258 | 1 | * @param string $namespace |
|
| 259 | * |
||
| 260 | 1 | * @return array |
|
| 261 | 1 | */ |
|
| 262 | 1 | private static function getGraphqlInterface(GQL\TypeInterface $interfaceAnnotation, array $classAnnotations, array $properties, array $methods, string $namespace) |
|
| 276 | 1 | ||
| 277 | 1 | /** |
|
| 278 | * Create a GraphQL Input type configuration from annotations on properties. |
||
| 279 | 1 | * |
|
| 280 | * @param string $shortClassName |
||
| 281 | * @param GQL\Input $inputAnnotation |
||
| 282 | * @param array $properties |
||
| 283 | * @param string $namespace |
||
| 284 | * |
||
| 285 | * @return array |
||
| 286 | */ |
||
| 287 | private static function getGraphqlInput(GQL\Input $inputAnnotation, array $classAnnotations, array $properties, string $namespace) |
||
| 297 | 1 | ||
| 298 | /** |
||
| 299 | * Get a Graphql scalar configuration from given scalar annotation. |
||
| 300 | * |
||
| 301 | * @param string $shortClassName |
||
| 302 | * @param string $className |
||
| 303 | * @param GQL\Scalar $scalarAnnotation |
||
| 304 | * @param array $classAnnotations |
||
| 305 | * |
||
| 306 | * @return array |
||
| 307 | */ |
||
| 308 | private static function getGraphqlScalar(string $className, GQL\Scalar $scalarAnnotation, array $classAnnotations) |
||
| 326 | |||
| 327 | /** |
||
| 328 | * Get a Graphql Enum configuration from given enum annotation. |
||
| 329 | 9 | * |
|
| 330 | 9 | * @param string $shortClassName |
|
| 331 | 9 | * @param GQL\Enum $enumAnnotation |
|
| 332 | 9 | * @param array $classAnnotations |
|
| 333 | 7 | * @param array $constants |
|
| 334 | * |
||
| 335 | * @return array |
||
| 336 | 9 | */ |
|
| 337 | private static function getGraphqlEnum(GQL\Enum $enumAnnotation, array $classAnnotations, array $constants) |
||
| 366 | 8 | ||
| 367 | 1 | /** |
|
| 368 | 1 | * Get a Graphql Union configuration from given union annotation. |
|
| 369 | 1 | * |
|
| 370 | 1 | * @param string $shortClassName |
|
| 371 | 1 | * @param GQL\Union $unionAnnotation |
|
| 372 | * @param array $classAnnotations |
||
| 373 | * |
||
| 374 | * @return array |
||
| 375 | */ |
||
| 376 | private static function getGraphqlUnion(GQL\Union $unionAnnotation, array $classAnnotations): array |
||
| 383 | 2 | ||
| 384 | /** |
||
| 385 | * Create Graphql fields configuration based on annotation. |
||
| 386 | * |
||
| 387 | * @param string $namespace |
||
| 388 | * @param array $propertiesOrMethods |
||
| 389 | 8 | * @param bool $isInput |
|
| 390 | 1 | * @param bool $isMethod |
|
| 391 | * @param string $currentValue |
||
| 392 | * |
||
| 393 | 8 | * @return array |
|
| 394 | 1 | */ |
|
| 395 | private static function getGraphqlFieldsFromAnnotations(string $namespace, array $propertiesOrMethods, bool $isInput = false, bool $isMethod = false, string $currentValue = 'value'): array |
||
| 516 | |||
| 517 | /** |
||
| 518 | * ArgTransformer |
||
| 519 | * Transform Arg type hint with enum as newObject(enumClassTypeHint, arg['a'])new EnumClass(arg['a']) |
||
| 520 | * Transform Arg type hint input as populate(InputClass, arg['a']). |
||
| 521 | */ |
||
| 522 | |||
| 523 | /** |
||
| 524 | * Return fields config from Provider methods. |
||
| 525 | * |
||
| 526 | * @param string $className |
||
| 527 | * @param array $methods |
||
| 528 | * @param bool $isMutation |
||
| 529 | * |
||
| 530 | * @return array |
||
| 531 | */ |
||
| 532 | private static function getGraphqlFieldsFromProvider(string $className, array $methods, bool $isMutation = false) |
||
| 570 | |||
| 571 | /** |
||
| 572 | * Get the config for description & deprecation reason. |
||
| 573 | * |
||
| 574 | * @param array $annotations |
||
| 575 | * @param bool $withDeprecation |
||
| 576 | * |
||
| 577 | * @return array |
||
| 578 | */ |
||
| 579 | private static function getDescriptionConfiguration(array $annotations, bool $withDeprecation = false) |
||
| 596 | |||
| 597 | /** |
||
| 598 | * Get args config from an array of @Arg annotation or by auto-guessing if a method is provided. |
||
| 599 | * |
||
| 600 | * @param array $args |
||
| 601 | * @param \ReflectionMethod $method |
||
| 602 | * |
||
| 603 | * @return array |
||
| 604 | */ |
||
| 605 | private static function getArgs(array $args = null, \ReflectionMethod $method = null) |
||
| 618 | |||
| 619 | private static function formatArgsForExpression(array $args) |
||
| 628 | |||
| 629 | /** |
||
| 630 | * Format an array of args to a list of arguments in an expression. |
||
| 631 | * |
||
| 632 | * @param array $args |
||
| 633 | * |
||
| 634 | * @return string |
||
| 635 | */ |
||
| 636 | /* |
||
| 637 | private static function formatArgsForExpression(array $args) |
||
| 638 | { |
||
| 639 | $resolvedArgs = []; |
||
| 640 | foreach ($args as $name => $config) { |
||
| 641 | $cleanedType = \str_replace(['[', ']', '!'], '', $config['type']); |
||
| 642 | $definition = self::resolveClassFromType($cleanedType); |
||
| 643 | $defaultFormat = \sprintf("args['%s']", $name); |
||
| 644 | if (!$definition) { |
||
| 645 | $resolvedArgs[] = $defaultFormat; |
||
| 646 | } else { |
||
| 647 | switch ($definition['type']) { |
||
| 648 | case 'input': |
||
| 649 | case 'enum': |
||
| 650 | $resolvedArgs[] = \sprintf("input('%s', args['%s'], '%s')", $config['type'], $name, $name); |
||
| 651 | break; |
||
| 652 | default: |
||
| 653 | $resolvedArgs[] = $defaultFormat; |
||
| 654 | break; |
||
| 655 | } |
||
| 656 | } |
||
| 657 | } |
||
| 658 | |||
| 659 | return sprintf("inputs(%s)", \implode(', ', $resolvedArgs)); |
||
| 660 | } |
||
| 661 | */ |
||
| 662 | |||
| 663 | /** |
||
| 664 | * Format a namespace to be used in an expression (double escape). |
||
| 665 | * |
||
| 666 | * @param string $namespace |
||
| 667 | * |
||
| 668 | * @return string |
||
| 669 | */ |
||
| 670 | private static function formatNamespaceForExpression(string $namespace) |
||
| 674 | |||
| 675 | /** |
||
| 676 | * Get the first annotation matching given class. |
||
| 677 | * |
||
| 678 | * @param array $annotations |
||
| 679 | * @param string|array $annotationClass |
||
| 680 | * |
||
| 681 | * @return mixed |
||
| 682 | */ |
||
| 683 | private static function getFirstAnnotationMatching(array $annotations, $annotationClass) |
||
| 699 | |||
| 700 | /** |
||
| 701 | * Format an expression (ie. add "@=" if not set). |
||
| 702 | * |
||
| 703 | * @param string $expression |
||
| 704 | * |
||
| 705 | * @return string |
||
| 706 | */ |
||
| 707 | private static function formatExpression(string $expression) |
||
| 711 | |||
| 712 | /** |
||
| 713 | * Suffix a name if it is not already. |
||
| 714 | * |
||
| 715 | * @param string $name |
||
| 716 | * @param string $suffix |
||
| 717 | * |
||
| 718 | * @return string |
||
| 719 | */ |
||
| 720 | private static function suffixName(string $name, string $suffix) |
||
| 724 | |||
| 725 | /** |
||
| 726 | * Try to guess a field type base on is annotations. |
||
| 727 | * |
||
| 728 | * @param string $namespace |
||
| 729 | * @param array $annotations |
||
| 730 | * |
||
| 731 | * @return string|false |
||
| 732 | */ |
||
| 733 | private static function guessType(string $namespace, array $annotations) |
||
| 778 | |||
| 779 | /** |
||
| 780 | * Resolve a FQN from classname and namespace. |
||
| 781 | * |
||
| 782 | * @param string $className |
||
| 783 | * @param string $namespace |
||
| 784 | * |
||
| 785 | * @return string |
||
| 786 | */ |
||
| 787 | public static function fullyQualifiedClassName(string $className, string $namespace) |
||
| 795 | |||
| 796 | /** |
||
| 797 | * Resolve a GraphqlType from a doctrine type. |
||
| 798 | * |
||
| 799 | * @param string $doctrineType |
||
| 800 | * |
||
| 801 | * @return string|false |
||
| 802 | */ |
||
| 803 | private static function resolveTypeFromDoctrineType(string $doctrineType) |
||
| 831 | |||
| 832 | /** |
||
| 833 | * Transform a method arguments from reflection to a list of GraphQL argument. |
||
| 834 | * |
||
| 835 | * @param \ReflectionMethod $method |
||
| 836 | * |
||
| 837 | * @return array |
||
| 838 | */ |
||
| 839 | private static function guessArgs(\ReflectionMethod $method) |
||
| 867 | |||
| 868 | /** |
||
| 869 | * Try to guess a GraphQL type from a Reflected Type. |
||
| 870 | * |
||
| 871 | * @param \ReflectionType $type |
||
| 872 | * |
||
| 873 | * @return string |
||
| 874 | */ |
||
| 875 | private static function resolveGraphqlTypeFromReflectionType(\ReflectionType $type, string $filterGraphqlType = null) |
||
| 892 | |||
| 893 | /** |
||
| 894 | * Resolve a GraphQL Type from a class name. |
||
| 895 | * |
||
| 896 | * @param string $className |
||
| 897 | * @param string $wantedType |
||
| 898 | * |
||
| 899 | * @return string|false |
||
| 900 | */ |
||
| 901 | private static function resolveTypeFromClass(string $className, string $wantedType = null) |
||
| 913 | |||
| 914 | /** |
||
| 915 | * Resolve a PHP class from a GraphQL type. |
||
| 916 | * |
||
| 917 | * @param string $type |
||
| 918 | * |
||
| 919 | * @return string|false |
||
| 920 | */ |
||
| 921 | private static function resolveClassFromType(string $type) |
||
| 925 | |||
| 926 | /** |
||
| 927 | * Convert a PHP Builtin type to a GraphQL type. |
||
| 928 | * |
||
| 929 | * @param string $phpType |
||
| 930 | * |
||
| 931 | * @return string |
||
| 932 | */ |
||
| 933 | private static function resolveTypeFromPhpType(string $phpType) |
||
| 951 | } |
||
| 952 |