vendor/webonyx/graphql-php/src/Utils/TypeInfo.php line 152

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace GraphQL\Utils;
  4. use GraphQL\Error\InvariantViolation;
  5. use GraphQL\Language\AST\ArgumentNode;
  6. use GraphQL\Language\AST\DirectiveNode;
  7. use GraphQL\Language\AST\EnumValueNode;
  8. use GraphQL\Language\AST\FieldNode;
  9. use GraphQL\Language\AST\FragmentDefinitionNode;
  10. use GraphQL\Language\AST\InlineFragmentNode;
  11. use GraphQL\Language\AST\ListTypeNode;
  12. use GraphQL\Language\AST\ListValueNode;
  13. use GraphQL\Language\AST\NamedTypeNode;
  14. use GraphQL\Language\AST\Node;
  15. use GraphQL\Language\AST\NonNullTypeNode;
  16. use GraphQL\Language\AST\ObjectFieldNode;
  17. use GraphQL\Language\AST\OperationDefinitionNode;
  18. use GraphQL\Language\AST\SelectionSetNode;
  19. use GraphQL\Language\AST\VariableDefinitionNode;
  20. use GraphQL\Type\Definition\CompositeType;
  21. use GraphQL\Type\Definition\Directive;
  22. use GraphQL\Type\Definition\EnumType;
  23. use GraphQL\Type\Definition\FieldArgument;
  24. use GraphQL\Type\Definition\FieldDefinition;
  25. use GraphQL\Type\Definition\HasFieldsType;
  26. use GraphQL\Type\Definition\ImplementingType;
  27. use GraphQL\Type\Definition\InputObjectType;
  28. use GraphQL\Type\Definition\InputType;
  29. use GraphQL\Type\Definition\InterfaceType;
  30. use GraphQL\Type\Definition\ListOfType;
  31. use GraphQL\Type\Definition\ObjectType;
  32. use GraphQL\Type\Definition\OutputType;
  33. use GraphQL\Type\Definition\Type;
  34. use GraphQL\Type\Definition\UnionType;
  35. use GraphQL\Type\Definition\WrappingType;
  36. use GraphQL\Type\Introspection;
  37. use GraphQL\Type\Schema;
  38. use function array_map;
  39. use function array_merge;
  40. use function array_pop;
  41. use function count;
  42. use function is_array;
  43. use function sprintf;
  44. class TypeInfo
  45. {
  46.     /** @var Schema */
  47.     private $schema;
  48.     /** @var array<(OutputType&Type)|null> */
  49.     private $typeStack;
  50.     /** @var array<(CompositeType&Type)|null> */
  51.     private $parentTypeStack;
  52.     /** @var array<(InputType&Type)|null> */
  53.     private $inputTypeStack;
  54.     /** @var array<FieldDefinition> */
  55.     private $fieldDefStack;
  56.     /** @var array<mixed> */
  57.     private $defaultValueStack;
  58.     /** @var Directive|null */
  59.     private $directive;
  60.     /** @var FieldArgument|null */
  61.     private $argument;
  62.     /** @var mixed */
  63.     private $enumValue;
  64.     /**
  65.      * @param Type|null $initialType
  66.      */
  67.     public function __construct(Schema $schema$initialType null)
  68.     {
  69.         $this->schema            $schema;
  70.         $this->typeStack         = [];
  71.         $this->parentTypeStack   = [];
  72.         $this->inputTypeStack    = [];
  73.         $this->fieldDefStack     = [];
  74.         $this->defaultValueStack = [];
  75.         if ($initialType === null) {
  76.             return;
  77.         }
  78.         if (Type::isInputType($initialType)) {
  79.             $this->inputTypeStack[] = $initialType;
  80.         }
  81.         if (Type::isCompositeType($initialType)) {
  82.             $this->parentTypeStack[] = $initialType;
  83.         }
  84.         if (! Type::isOutputType($initialType)) {
  85.             return;
  86.         }
  87.         $this->typeStack[] = $initialType;
  88.     }
  89.     /**
  90.      * @deprecated moved to GraphQL\Utils\TypeComparators
  91.      *
  92.      * @codeCoverageIgnore
  93.      */
  94.     public static function isEqualType(Type $typeAType $typeB) : bool
  95.     {
  96.         return TypeComparators::isEqualType($typeA$typeB);
  97.     }
  98.     /**
  99.      * @deprecated moved to GraphQL\Utils\TypeComparators
  100.      *
  101.      * @codeCoverageIgnore
  102.      */
  103.     public static function isTypeSubTypeOf(Schema $schemaType $maybeSubTypeType $superType)
  104.     {
  105.         return TypeComparators::isTypeSubTypeOf($schema$maybeSubType$superType);
  106.     }
  107.     /**
  108.      * @deprecated moved to GraphQL\Utils\TypeComparators
  109.      *
  110.      * @codeCoverageIgnore
  111.      */
  112.     public static function doTypesOverlap(Schema $schemaCompositeType $typeACompositeType $typeB)
  113.     {
  114.         return TypeComparators::doTypesOverlap($schema$typeA$typeB);
  115.     }
  116.     /**
  117.      * Given root type scans through all fields to find nested types. Returns array where keys are for type name
  118.      * and value contains corresponding type instance.
  119.      *
  120.      * Example output:
  121.      * [
  122.      *     'String' => $instanceOfStringType,
  123.      *     'MyType' => $instanceOfMyType,
  124.      *     ...
  125.      * ]
  126.      *
  127.      * @param Type|null   $type
  128.      * @param Type[]|null $typeMap
  129.      *
  130.      * @return Type[]|null
  131.      */
  132.     public static function extractTypes($type, ?array $typeMap null)
  133.     {
  134.         if (! $typeMap) {
  135.             $typeMap = [];
  136.         }
  137.         if (! $type) {
  138.             return $typeMap;
  139.         }
  140.         if ($type instanceof WrappingType) {
  141.             return self::extractTypes($type->getWrappedType(true), $typeMap);
  142.         }
  143.         if (! $type instanceof Type) {
  144.             // Preserve these invalid types in map (at numeric index) to make them
  145.             // detectable during $schema->validate()
  146.             $i            0;
  147.             $alreadyInMap false;
  148.             while (isset($typeMap[$i])) {
  149.                 $alreadyInMap $alreadyInMap || $typeMap[$i] === $type;
  150.                 $i++;
  151.             }
  152.             if (! $alreadyInMap) {
  153.                 $typeMap[$i] = $type;
  154.             }
  155.             return $typeMap;
  156.         }
  157.         if (isset($typeMap[$type->name])) {
  158.             Utils::invariant(
  159.                 $typeMap[$type->name] === $type,
  160.                 sprintf('Schema must contain unique named types but contains multiple types named "%s" '$type) .
  161.                 '(see http://webonyx.github.io/graphql-php/type-system/#type-registry).'
  162.             );
  163.             return $typeMap;
  164.         }
  165.         $typeMap[$type->name] = $type;
  166.         $nestedTypes = [];
  167.         if ($type instanceof UnionType) {
  168.             $nestedTypes $type->getTypes();
  169.         }
  170.         if ($type instanceof ImplementingType) {
  171.             $nestedTypes array_merge($nestedTypes$type->getInterfaces());
  172.         }
  173.         if ($type instanceof HasFieldsType) {
  174.             foreach ($type->getFields() as $field) {
  175.                 if (count($field->args) > 0) {
  176.                     $fieldArgTypes array_map(
  177.                         static function (FieldArgument $arg) : Type {
  178.                             return $arg->getType();
  179.                         },
  180.                         $field->args
  181.                     );
  182.                     $nestedTypes array_merge($nestedTypes$fieldArgTypes);
  183.                 }
  184.                 $nestedTypes[] = $field->getType();
  185.             }
  186.         }
  187.         if ($type instanceof InputObjectType) {
  188.             foreach ($type->getFields() as $field) {
  189.                 $nestedTypes[] = $field->getType();
  190.             }
  191.         }
  192.         foreach ($nestedTypes as $nestedType) {
  193.             $typeMap self::extractTypes($nestedType$typeMap);
  194.         }
  195.         return $typeMap;
  196.     }
  197.     /**
  198.      * @param Type[] $typeMap
  199.      *
  200.      * @return Type[]
  201.      */
  202.     public static function extractTypesFromDirectives(Directive $directive, array $typeMap = [])
  203.     {
  204.         if (is_array($directive->args)) {
  205.             foreach ($directive->args as $arg) {
  206.                 $typeMap self::extractTypes($arg->getType(), $typeMap);
  207.             }
  208.         }
  209.         return $typeMap;
  210.     }
  211.     /**
  212.      * @return (Type&InputType)|null
  213.      */
  214.     public function getParentInputType() : ?InputType
  215.     {
  216.         return $this->inputTypeStack[count($this->inputTypeStack) - 2] ?? null;
  217.     }
  218.     public function getArgument() : ?FieldArgument
  219.     {
  220.         return $this->argument;
  221.     }
  222.     /**
  223.      * @return mixed
  224.      */
  225.     public function getEnumValue()
  226.     {
  227.         return $this->enumValue;
  228.     }
  229.     public function enter(Node $node)
  230.     {
  231.         $schema $this->schema;
  232.         // Note: many of the types below are explicitly typed as "mixed" to drop
  233.         // any assumptions of a valid schema to ensure runtime types are properly
  234.         // checked before continuing since TypeInfo is used as part of validation
  235.         // which occurs before guarantees of schema and document validity.
  236.         switch (true) {
  237.             case $node instanceof SelectionSetNode:
  238.                 $namedType               Type::getNamedType($this->getType());
  239.                 $this->parentTypeStack[] = Type::isCompositeType($namedType) ? $namedType null;
  240.                 break;
  241.             case $node instanceof FieldNode:
  242.                 $parentType $this->getParentType();
  243.                 $fieldDef   null;
  244.                 if ($parentType) {
  245.                     $fieldDef self::getFieldDefinition($schema$parentType$node);
  246.                 }
  247.                 $fieldType null;
  248.                 if ($fieldDef) {
  249.                     $fieldType $fieldDef->getType();
  250.                 }
  251.                 $this->fieldDefStack[] = $fieldDef;
  252.                 $this->typeStack[]     = Type::isOutputType($fieldType) ? $fieldType null;
  253.                 break;
  254.             case $node instanceof DirectiveNode:
  255.                 $this->directive $schema->getDirective($node->name->value);
  256.                 break;
  257.             case $node instanceof OperationDefinitionNode:
  258.                 $type null;
  259.                 if ($node->operation === 'query') {
  260.                     $type $schema->getQueryType();
  261.                 } elseif ($node->operation === 'mutation') {
  262.                     $type $schema->getMutationType();
  263.                 } elseif ($node->operation === 'subscription') {
  264.                     $type $schema->getSubscriptionType();
  265.                 }
  266.                 $this->typeStack[] = Type::isOutputType($type) ? $type null;
  267.                 break;
  268.             case $node instanceof InlineFragmentNode:
  269.             case $node instanceof FragmentDefinitionNode:
  270.                 $typeConditionNode $node->typeCondition;
  271.                 $outputType        $typeConditionNode
  272.                     self::typeFromAST(
  273.                         $schema,
  274.                         $typeConditionNode
  275.                     )
  276.                     : Type::getNamedType($this->getType());
  277.                 $this->typeStack[] = Type::isOutputType($outputType) ? $outputType null;
  278.                 break;
  279.             case $node instanceof VariableDefinitionNode:
  280.                 $inputType              self::typeFromAST($schema$node->type);
  281.                 $this->inputTypeStack[] = Type::isInputType($inputType) ? $inputType null// push
  282.                 break;
  283.             case $node instanceof ArgumentNode:
  284.                 $fieldOrDirective $this->getDirective() ?? $this->getFieldDef();
  285.                 $argDef           $argType null;
  286.                 if ($fieldOrDirective) {
  287.                     /** @var FieldArgument $argDef */
  288.                     $argDef Utils::find(
  289.                         $fieldOrDirective->args,
  290.                         static function ($arg) use ($node) : bool {
  291.                             return $arg->name === $node->name->value;
  292.                         }
  293.                     );
  294.                     if ($argDef !== null) {
  295.                         $argType $argDef->getType();
  296.                     }
  297.                 }
  298.                 $this->argument            $argDef;
  299.                 $this->defaultValueStack[] = $argDef && $argDef->defaultValueExists() ? $argDef->defaultValue Utils::undefined();
  300.                 $this->inputTypeStack[]    = Type::isInputType($argType) ? $argType null;
  301.                 break;
  302.             case $node instanceof ListValueNode:
  303.                 $type     $this->getInputType();
  304.                 $listType $type === null null Type::getNullableType($type);
  305.                 $itemType $listType instanceof ListOfType
  306.                     $listType->getWrappedType()
  307.                     : $listType;
  308.                 // List positions never have a default value.
  309.                 $this->defaultValueStack[] = Utils::undefined();
  310.                 $this->inputTypeStack[]    = Type::isInputType($itemType) ? $itemType null;
  311.                 break;
  312.             case $node instanceof ObjectFieldNode:
  313.                 $objectType     Type::getNamedType($this->getInputType());
  314.                 $fieldType      null;
  315.                 $inputField     null;
  316.                 $inputFieldType null;
  317.                 if ($objectType instanceof InputObjectType) {
  318.                     $tmp            $objectType->getFields();
  319.                     $inputField     $tmp[$node->name->value] ?? null;
  320.                     $inputFieldType $inputField $inputField->getType() : null;
  321.                 }
  322.                 $this->defaultValueStack[] = $inputField && $inputField->defaultValueExists() ? $inputField->defaultValue Utils::undefined();
  323.                 $this->inputTypeStack[]    = Type::isInputType($inputFieldType) ? $inputFieldType null;
  324.                 break;
  325.             case $node instanceof EnumValueNode:
  326.                 $enumType  Type::getNamedType($this->getInputType());
  327.                 $enumValue null;
  328.                 if ($enumType instanceof EnumType) {
  329.                     $this->enumValue $enumType->getValue($node->value);
  330.                 }
  331.                 $this->enumValue $enumValue;
  332.                 break;
  333.         }
  334.     }
  335.     /**
  336.      * @return (Type & OutputType) | null
  337.      */
  338.     public function getType() : ?OutputType
  339.     {
  340.         return $this->typeStack[count($this->typeStack) - 1] ?? null;
  341.     }
  342.     /**
  343.      * @return (CompositeType & Type) | null
  344.      */
  345.     public function getParentType() : ?CompositeType
  346.     {
  347.         return $this->parentTypeStack[count($this->parentTypeStack) - 1] ?? null;
  348.     }
  349.     /**
  350.      * Not exactly the same as the executor's definition of getFieldDef, in this
  351.      * statically evaluated environment we do not always have an Object type,
  352.      * and need to handle Interface and Union types.
  353.      */
  354.     private static function getFieldDefinition(Schema $schemaType $parentTypeFieldNode $fieldNode) : ?FieldDefinition
  355.     {
  356.         $name       $fieldNode->name->value;
  357.         $schemaMeta Introspection::schemaMetaFieldDef();
  358.         if ($name === $schemaMeta->name && $schema->getQueryType() === $parentType) {
  359.             return $schemaMeta;
  360.         }
  361.         $typeMeta Introspection::typeMetaFieldDef();
  362.         if ($name === $typeMeta->name && $schema->getQueryType() === $parentType) {
  363.             return $typeMeta;
  364.         }
  365.         $typeNameMeta Introspection::typeNameMetaFieldDef();
  366.         if ($name === $typeNameMeta->name && $parentType instanceof CompositeType) {
  367.             return $typeNameMeta;
  368.         }
  369.         if ($parentType instanceof ObjectType ||
  370.             $parentType instanceof InterfaceType
  371.         ) {
  372.             return $parentType->findField($name);
  373.         }
  374.         return null;
  375.     }
  376.     /**
  377.      * @param NamedTypeNode|ListTypeNode|NonNullTypeNode $inputTypeNode
  378.      *
  379.      * @throws InvariantViolation
  380.      */
  381.     public static function typeFromAST(Schema $schema$inputTypeNode) : ?Type
  382.     {
  383.         return AST::typeFromAST($schema$inputTypeNode);
  384.     }
  385.     public function getDirective() : ?Directive
  386.     {
  387.         return $this->directive;
  388.     }
  389.     public function getFieldDef() : ?FieldDefinition
  390.     {
  391.         return $this->fieldDefStack[count($this->fieldDefStack) - 1] ?? null;
  392.     }
  393.     /**
  394.      * @return mixed|null
  395.      */
  396.     public function getDefaultValue()
  397.     {
  398.         return $this->defaultValueStack[count($this->defaultValueStack) - 1] ?? null;
  399.     }
  400.     /**
  401.      * @return (Type & InputType) | null
  402.      */
  403.     public function getInputType() : ?InputType
  404.     {
  405.         return $this->inputTypeStack[count($this->inputTypeStack) - 1] ?? null;
  406.     }
  407.     public function leave(Node $node)
  408.     {
  409.         switch (true) {
  410.             case $node instanceof SelectionSetNode:
  411.                 array_pop($this->parentTypeStack);
  412.                 break;
  413.             case $node instanceof FieldNode:
  414.                 array_pop($this->fieldDefStack);
  415.                 array_pop($this->typeStack);
  416.                 break;
  417.             case $node instanceof DirectiveNode:
  418.                 $this->directive null;
  419.                 break;
  420.             case $node instanceof OperationDefinitionNode:
  421.             case $node instanceof InlineFragmentNode:
  422.             case $node instanceof FragmentDefinitionNode:
  423.                 array_pop($this->typeStack);
  424.                 break;
  425.             case $node instanceof VariableDefinitionNode:
  426.                 array_pop($this->inputTypeStack);
  427.                 break;
  428.             case $node instanceof ArgumentNode:
  429.                 $this->argument null;
  430.                 array_pop($this->defaultValueStack);
  431.                 array_pop($this->inputTypeStack);
  432.                 break;
  433.             case $node instanceof ListValueNode:
  434.             case $node instanceof ObjectFieldNode:
  435.                 array_pop($this->defaultValueStack);
  436.                 array_pop($this->inputTypeStack);
  437.                 break;
  438.             case $node instanceof EnumValueNode:
  439.                 $this->enumValue null;
  440.                 break;
  441.         }
  442.     }
  443. }