diff --git a/benchmarks/ScalarOverrideBench.php b/benchmarks/ScalarOverrideBench.php index e6e6c0b03..34758985c 100644 --- a/benchmarks/ScalarOverrideBench.php +++ b/benchmarks/ScalarOverrideBench.php @@ -60,6 +60,7 @@ public function setUp(): void $this->schemaTypeLoader = new Schema([ 'query' => $queryTypeLoader, 'typeLoader' => static fn (string $name): ?Type => $typesForLoader[$name] ?? null, + 'typeLoaderSupportsScalars' => true, ]); $queryTypeTypes = new ObjectType([ diff --git a/docs/class-reference.md b/docs/class-reference.md index 21b10dc2c..81b7cd30c 100644 --- a/docs/class-reference.md +++ b/docs/class-reference.md @@ -807,6 +807,7 @@ Usage example: types?: Types|null, directives?: array|null, typeLoader?: TypeLoader|null, + typeLoaderSupportsScalars?: bool|null, assumeValid?: bool|null, astNode?: SchemaDefinitionNode|null, extensionASTNodes?: array|null, diff --git a/examples/06-per-schema-scalar-override/example.php b/examples/06-per-schema-scalar-override/example.php index 9aa5bd87b..9f5f81078 100644 --- a/examples/06-per-schema-scalar-override/example.php +++ b/examples/06-per-schema-scalar-override/example.php @@ -56,6 +56,7 @@ $schemaViaTypeLoader = new Schema([ 'query' => $queryTypeForLoader, 'typeLoader' => static fn (string $name): ?Type => $types[$name] ?? null, + 'typeLoaderSupportsScalars' => true, ]); $result = GraphQL::executeQuery($schemaViaTypeLoader, '{ greeting(name: "World") }'); diff --git a/src/Type/Schema.php b/src/Type/Schema.php index 8708552b4..ae3448311 100644 --- a/src/Type/Schema.php +++ b/src/Type/Schema.php @@ -188,13 +188,14 @@ public function getTypeMap(): array $allReferencedTypes[$name] = $override; } - if (isset($this->config->typeLoader)) { + $typeLoader = $this->config->getTypeLoader(); + if ($typeLoader && $this->config->getTypeLoaderSupportsScalars()) { foreach (Type::BUILT_IN_SCALAR_NAMES as $scalarName) { if (isset($scalarOverrides[$scalarName])) { continue; } - $type = ($this->config->typeLoader)($scalarName); + $type = $typeLoader($scalarName); if ($type instanceof ScalarType && $type->name === $scalarName && $type !== $builtInScalars[$scalarName] @@ -362,11 +363,16 @@ public function hasType(string $name): bool */ private function loadType(string $typeName): ?Type { - if (! isset($this->config->typeLoader)) { + $typeLoader = $this->config->getTypeLoader(); + if (! $typeLoader) { return $this->getTypeMap()[$typeName] ?? null; } - $type = ($this->config->typeLoader)($typeName); + if (! $this->config->getTypeLoaderSupportsScalars() && in_array($typeName, Type::BUILT_IN_SCALAR_NAMES)) { + return null; + } + + $type = $typeLoader($typeName); if ($type === null) { return null; } diff --git a/src/Type/SchemaConfig.php b/src/Type/SchemaConfig.php index abf82dd4f..c1d830bdf 100644 --- a/src/Type/SchemaConfig.php +++ b/src/Type/SchemaConfig.php @@ -37,6 +37,7 @@ * types?: Types|null, * directives?: array|null, * typeLoader?: TypeLoader|null, + * typeLoaderSupportsScalars?: bool|null, * assumeValid?: bool|null, * astNode?: SchemaDefinitionNode|null, * extensionASTNodes?: array|null, @@ -72,6 +73,8 @@ class SchemaConfig */ public $typeLoader; + public bool $typeLoaderSupportsScalars = false; + public bool $assumeValid = false; public ?SchemaDefinitionNode $astNode = null; @@ -121,6 +124,10 @@ public static function create(array $options = []): self $config->setTypeLoader($options['typeLoader']); } + if (isset($options['typeLoaderSupportsScalars'])) { + $config->setTypeLoaderSupportsScalars($options['typeLoaderSupportsScalars']); + } + if (isset($options['assumeValid'])) { $config->setAssumeValid($options['assumeValid']); } @@ -298,6 +305,20 @@ public function setTypeLoader(?callable $typeLoader): self return $this; } + /** @api */ + public function getTypeLoaderSupportsScalars(): bool + { + return $this->typeLoaderSupportsScalars; + } + + /** @api */ + public function setTypeLoaderSupportsScalars(bool $supportsScalars): self + { + $this->typeLoaderSupportsScalars = $supportsScalars; + + return $this; + } + public function getAssumeValid(): bool { return $this->assumeValid; diff --git a/tests/Type/ScalarOverridesTest.php b/tests/Type/ScalarOverridesTest.php index ebee4ca88..d025ca2cb 100644 --- a/tests/Type/ScalarOverridesTest.php +++ b/tests/Type/ScalarOverridesTest.php @@ -43,6 +43,7 @@ public function testTypeLoaderOverrideWorksEndToEnd(): void $schema = new Schema([ 'query' => $queryType, 'typeLoader' => static fn (string $name): ?Type => $types[$name] ?? null, + 'typeLoaderSupportsScalars' => true, ]); $schema->assertValid(); @@ -65,6 +66,7 @@ public function testTypeLoaderOverrideWorksInProductionMode(): void $schema = new Schema([ 'query' => $queryType, 'typeLoader' => static fn (string $name): ?Type => $types[$name] ?? null, + 'typeLoaderSupportsScalars' => true, ]); $result = GraphQL::executeQuery($schema, '{ greeting }'); @@ -117,6 +119,7 @@ public function testIntrospectionUsesOverriddenScalar(): void $schema = new Schema([ 'query' => $queryType, 'typeLoader' => static fn (string $name): ?Type => $types[$name] ?? null, + 'typeLoaderSupportsScalars' => true, ]); $result = GraphQL::executeQuery($schema, '{ __type(name: "Query") { fields { name } } }'); @@ -128,6 +131,28 @@ public function testIntrospectionUsesOverriddenScalar(): void self::assertContains('GREETING', $fieldNames); } + public function testTypeLoaderNotInvokedForScalarsWhenOptInNotSet(): void + { + $invocations = []; + $uppercaseString = self::createUppercaseString(); + $queryType = self::createQueryType(); + $types = ['Query' => $queryType, 'String' => $uppercaseString]; + + $schema = new Schema([ + 'query' => $queryType, + 'typeLoader' => static function (string $name) use ($types, &$invocations): ?Type { + $invocations[] = $name; + + return $types[$name] ?? null; + }, + ]); + + $result = GraphQL::executeQuery($schema, '{ greeting }'); + + self::assertSame(['data' => ['greeting' => 'hello world']], $result->toArray()); + self::assertNotContains('String', $invocations); + } + public function testTwoSchemasWithDifferentOverridesAreIndependent(): void { $uppercaseString = new CustomScalarType([ @@ -151,6 +176,7 @@ public function testTwoSchemasWithDifferentOverridesAreIndependent(): void $schemaB = new Schema([ 'query' => $queryTypeB, 'typeLoader' => static fn (string $name): ?Type => $typesB[$name] ?? null, + 'typeLoaderSupportsScalars' => true, ]); $resultA = GraphQL::executeQuery($schemaA, '{ greeting }'); @@ -194,6 +220,7 @@ public function testNonOverriddenScalarsAreUnaffected(): void $schema = new Schema([ 'query' => $queryType, 'typeLoader' => static fn (string $name): ?Type => $types[$name] ?? null, + 'typeLoaderSupportsScalars' => true, ]); $result = GraphQL::executeQuery($schema, '{ greeting count ratio active identifier }');