Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/class-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -2955,7 +2955,8 @@ static function astFromValue($value, GraphQL\Type\Definition\InputType $type): ?
static function valueFromAST(
?GraphQL\Language\AST\ValueNode $valueNode,
GraphQL\Type\Definition\Type $type,
?array $variables = null
?array $variables = null,
?GraphQL\Type\Schema $schema = null
)
```

Expand Down
11 changes: 8 additions & 3 deletions src/Executor/ReferenceExecutor.php
Original file line number Diff line number Diff line change
Expand Up @@ -466,10 +466,13 @@ protected function shouldIncludeNode(SelectionNode $node): bool
{
$variableValues = $this->exeContext->variableValues;

$schema = $this->exeContext->schema;

$skip = Values::getDirectiveValues(
Directive::skipDirective(),
$node,
$variableValues
$variableValues,
$schema,
);
if (isset($skip['if']) && $skip['if'] === true) {
return false;
Expand All @@ -478,7 +481,8 @@ protected function shouldIncludeNode(SelectionNode $node): bool
$include = Values::getDirectiveValues(
Directive::includeDirective(),
$node,
$variableValues
$variableValues,
$schema,
);

return ! isset($include['if']) || $include['if'] !== false;
Expand Down Expand Up @@ -738,7 +742,8 @@ protected function resolveFieldValueOrError(
$args = $this->fieldArgsCache[$fieldDef][$fieldNode] ??= $argsMapper(Values::getArgumentValues(
$fieldDef,
$fieldNode,
$this->exeContext->variableValues
$this->exeContext->variableValues,
$this->exeContext->schema,
), $fieldDef, $fieldNode, $contextValue);

return $resolveFn($rootValue, $args, $contextValue, $info);
Expand Down
12 changes: 6 additions & 6 deletions src/Executor/Values.php
Original file line number Diff line number Diff line change
Expand Up @@ -154,13 +154,13 @@ public static function getVariableValues(Schema $schema, NodeList $varDefNodes,
*
* @return array<string, mixed>|null
*/
public static function getDirectiveValues(Directive $directiveDef, Node $node, ?array $variableValues = null): ?array
public static function getDirectiveValues(Directive $directiveDef, Node $node, ?array $variableValues = null, ?Schema $schema = null): ?array
{
$directiveDefName = $directiveDef->name;

foreach ($node->directives as $directive) {
if ($directive->name->value === $directiveDefName) {
return self::getArgumentValues($directiveDef, $directive, $variableValues);
return self::getArgumentValues($directiveDef, $directive, $variableValues, $schema);
}
}

Expand All @@ -180,7 +180,7 @@ public static function getDirectiveValues(Directive $directiveDef, Node $node, ?
*
* @return array<string, mixed>
*/
public static function getArgumentValues($def, Node $node, ?array $variableValues = null): array
public static function getArgumentValues($def, Node $node, ?array $variableValues = null, ?Schema $schema = null): array
{
if ($def->args === []) {
return [];
Expand All @@ -196,7 +196,7 @@ public static function getArgumentValues($def, Node $node, ?array $variableValue
}
}

return static::getArgumentValuesForMap($def, $argumentValueMap, $variableValues, $node);
return static::getArgumentValuesForMap($def, $argumentValueMap, $variableValues, $node, $schema);
}

/**
Expand All @@ -209,7 +209,7 @@ public static function getArgumentValues($def, Node $node, ?array $variableValue
*
* @return array<string, mixed>
*/
public static function getArgumentValuesForMap($def, array $argumentValueMap, ?array $variableValues = null, ?Node $referenceNode = null): array
public static function getArgumentValuesForMap($def, array $argumentValueMap, ?array $variableValues = null, ?Node $referenceNode = null, ?Schema $schema = null): array
{
/** @var array<string, mixed> $coercedValues */
$coercedValues = [];
Expand Down Expand Up @@ -260,7 +260,7 @@ public static function getArgumentValuesForMap($def, array $argumentValueMap, ?a
// usage here is of the correct type.
$coercedValues[$name] = $variableValues[$variableName] ?? null;
} else {
$coercedValue = AST::valueFromAST($argumentValueNode, $argType, $variableValues);
$coercedValue = AST::valueFromAST($argumentValueNode, $argType, $variableValues, $schema);
if (Utils::undefined() === $coercedValue) {
// Note: ValuesOfCorrectType validation should catch this before
// execution. This is a runtime check to ensure execution does not
Expand Down
2 changes: 1 addition & 1 deletion src/Type/Definition/Type.php
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ public static function overrideStandardTypes(array $types): void
public static function isBuiltInScalar($type): bool
{
return $type instanceof ScalarType
&& in_array($type->name, self::BUILT_IN_SCALAR_NAMES, true);
&& self::isBuiltInScalarName($type->name);
}

/** Checks if the given name is one of the built-in scalar type names (ID, String, Int, Float, Boolean). */
Expand Down
22 changes: 17 additions & 5 deletions src/Utils/AST.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
use GraphQL\Type\Definition\NullableType;
use GraphQL\Type\Definition\ScalarType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;

/**
* Various utilities dealing with AST.
Expand Down Expand Up @@ -307,7 +308,7 @@ public static function astFromValue($value, InputType $type): ?ValueNode
*
* @api
*/
public static function valueFromAST(?ValueNode $valueNode, Type $type, ?array $variables = null)
public static function valueFromAST(?ValueNode $valueNode, Type $type, ?array $variables = null, ?Schema $schema = null)
{
$undefined = Utils::undefined();

Expand All @@ -323,7 +324,7 @@ public static function valueFromAST(?ValueNode $valueNode, Type $type, ?array $v
return $undefined;
}

return self::valueFromAST($valueNode, $type->getWrappedType(), $variables);
return self::valueFromAST($valueNode, $type->getWrappedType(), $variables, $schema);
}

if ($valueNode instanceof NullValueNode) {
Expand Down Expand Up @@ -362,7 +363,7 @@ public static function valueFromAST(?ValueNode $valueNode, Type $type, ?array $v

$coercedValues[] = null;
} else {
$itemValue = self::valueFromAST($itemNode, $itemType, $variables);
$itemValue = self::valueFromAST($itemNode, $itemType, $variables, $schema);
if ($undefined === $itemValue) {
// Invalid: intentionally return no value.
return $undefined;
Expand All @@ -375,7 +376,7 @@ public static function valueFromAST(?ValueNode $valueNode, Type $type, ?array $v
return $coercedValues;
}

$coercedValue = self::valueFromAST($valueNode, $itemType, $variables);
$coercedValue = self::valueFromAST($valueNode, $itemType, $variables, $schema);
if ($undefined === $coercedValue) {
// Invalid: intentionally return no value.
return $undefined;
Expand Down Expand Up @@ -416,7 +417,8 @@ public static function valueFromAST(?ValueNode $valueNode, Type $type, ?array $v
$fieldValue = self::valueFromAST(
$fieldNode->value,
$field->getType(),
$variables
$variables,
$schema,
);

if ($undefined === $fieldValue) {
Expand All @@ -439,6 +441,16 @@ public static function valueFromAST(?ValueNode $valueNode, Type $type, ?array $v
}

assert($type instanceof ScalarType, 'only remaining option');
$typeName = $type->name;

// Account for type loader returning a different scalar instance than
// the built-in singleton used in field definitions. Resolve the actual
// type from the schema to ensure the correct parseLiteral() is called.
if ($schema !== null && Type::isBuiltInScalarName($typeName)) {
$schemaType = $schema->getType($typeName);
assert($schemaType instanceof ScalarType, "Schema must provide a ScalarType for built-in scalar \"{$typeName}\".");
$type = $schemaType;
}

// Scalars fulfill parsing a literal value via parseLiteral().
// Invalid values represent a failure to parse correctly, in which case
Expand Down
16 changes: 16 additions & 0 deletions tests/Type/Definition/TypeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,20 @@ public function testIsBuiltInScalarReturnsFalseForCustomScalarWithNonBuiltInName
{
self::assertFalse(Type::isBuiltInScalar(new CustomScalarType(['name' => 'MyScalar']))); // @phpstan-ignore staticMethod.alreadyNarrowedType
}

public function testIsBuiltInScalarNameReturnsTrueForBuiltInNames(): void
{
self::assertTrue(Type::isBuiltInScalarName(Type::STRING));
self::assertTrue(Type::isBuiltInScalarName(Type::INT));
self::assertTrue(Type::isBuiltInScalarName(Type::FLOAT));
self::assertTrue(Type::isBuiltInScalarName(Type::BOOLEAN));
self::assertTrue(Type::isBuiltInScalarName(Type::ID));
}

public function testIsBuiltInScalarNameReturnsFalseForNonBuiltInNames(): void
{
self::assertFalse(Type::isBuiltInScalarName('MyScalar'));
self::assertFalse(Type::isBuiltInScalarName('Query'));
self::assertFalse(Type::isBuiltInScalarName(''));
}
}
83 changes: 83 additions & 0 deletions tests/Type/ScalarOverridesTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

use GraphQL\Error\InvariantViolation;
use GraphQL\GraphQL;
use GraphQL\Language\AST\BooleanValueNode;
use GraphQL\Language\AST\IntValueNode;
use GraphQL\Language\AST\StringValueNode;
use GraphQL\Type\Definition\CustomScalarType;
use GraphQL\Type\Definition\InputObjectType;
Expand Down Expand Up @@ -298,6 +300,87 @@ public function testTypesOverrideWithInputObjectFieldOfOverriddenBuiltInScalarTy
self::assertSame(['data' => ['node' => 'custom-abc-123:test']], $result->toArray());
}

public function testTypesOverrideCallsParseLiteralForInlineArgument(): void
{
$parseLiteralCalled = false;

$customID = new CustomScalarType([
'name' => Type::ID,
'serialize' => static fn ($value): string => (string) $value,
'parseValue' => static fn ($value): string => 'parsed-' . $value,
'parseLiteral' => static function ($node) use (&$parseLiteralCalled): string {
$parseLiteralCalled = true;

assert($node instanceof IntValueNode || $node instanceof StringValueNode);

return 'literal-' . $node->value;
},
]);

$queryType = new ObjectType([
'name' => 'Query',
'fields' => [
'node' => [
'type' => Type::string(),
'args' => [
'id' => Type::nonNull(Type::id()),
],
'resolve' => static fn ($root, array $args): string => 'node-' . $args['id'],
],
],
]);

$schema = new Schema([
'query' => $queryType,
'types' => [$customID],
]);

$result = GraphQL::executeQuery($schema, '{ node(id: 123) }');

self::assertEmpty($result->errors, isset($result->errors[0]) ? $result->errors[0]->getMessage() : '');
self::assertTrue($parseLiteralCalled, 'Expected custom parseLiteral to be called for inline literal argument');
self::assertSame(['data' => ['node' => 'node-literal-123']], $result->toArray());
}

public function testTypesOverrideCallsParseLiteralForDirectiveArgument(): void
{
$parseLiteralCalled = false;

$customBoolean = new CustomScalarType([
'name' => Type::BOOLEAN,
'serialize' => static fn ($value): bool => (bool) $value,
'parseValue' => static fn ($value): bool => (bool) $value,
'parseLiteral' => static function ($node) use (&$parseLiteralCalled): bool {
$parseLiteralCalled = true;

self::assertInstanceOf(BooleanValueNode::class, $node);

return $node->value;
},
]);

$queryType = new ObjectType([
'name' => 'Query',
'fields' => [
'greeting' => [
'type' => Type::string(),
'resolve' => static fn (): string => 'hello',
],
],
]);

$schema = new Schema([
'query' => $queryType,
'types' => [$customBoolean],
]);

$result = GraphQL::executeQuery($schema, '{ greeting @include(if: true) }');

self::assertEmpty($result->errors, isset($result->errors[0]) ? $result->errors[0]->getMessage() : '');
self::assertTrue($parseLiteralCalled, 'Expected custom parseLiteral to be called for directive argument');
self::assertSame(['data' => ['greeting' => 'hello']], $result->toArray());
}

public function testTypesOverrideWorksWithTypeLoader(): void
{
$uppercaseString = self::createUppercaseString();
Expand Down
Loading