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

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 $typeA, Type $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 $schema, Type $maybeSubType, Type $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 $schema, CompositeType $typeA, CompositeType $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 $schema, Type $parentType, FieldNode $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. }