From b524b65ec62be1929ec24ab0703a199983031ded Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Moitie=CC=81?= Date: Thu, 11 Jun 2026 12:54:29 +0100 Subject: [PATCH] Pass scalar overrides explicitly to keep lazy type loading lazy On webonyx/graphql-php >= 15.31.0, the first lookup of a built-in scalar triggers scalar-override discovery, which resolves the lazy types callable and eagerly builds every type in the schema from the AST on every request. When SchemaConfig::setScalarOverrides is available, determine the overrides cheaply from the document AST and pass them explicitly so the scan is skipped and the types callable stays unresolved. Fixes #2771. Co-Authored-By: Claude Fable 5 --- CHANGELOG.md | 4 ++ src/Schema/SchemaBuilder.php | 18 ++++++++ tests/Unit/Schema/SchemaBuilderTest.php | 55 +++++++++++++++++++++++++ 3 files changed, 77 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 65a8992ad8..f8975c0931 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,10 @@ You can find and compare releases at the [GitHub release page](https://github.co ## Unreleased +### Fixed + +- Keep lazy type loading lazy on `webonyx/graphql-php >= 15.31.0` by passing scalar overrides explicitly when `SchemaConfig::setScalarOverrides` is available https://github.com/nuwave/lighthouse/pull/2772 + ## v6.67.0 ### Changed diff --git a/src/Schema/SchemaBuilder.php b/src/Schema/SchemaBuilder.php index b6df5884f1..d5bdce4395 100644 --- a/src/Schema/SchemaBuilder.php +++ b/src/Schema/SchemaBuilder.php @@ -4,6 +4,7 @@ use GraphQL\GraphQL; use GraphQL\Type\Definition\ObjectType; +use GraphQL\Type\Definition\ScalarType; use GraphQL\Type\Definition\Type; use GraphQL\Type\Schema; use GraphQL\Type\SchemaConfig; @@ -70,6 +71,23 @@ protected function build(DocumentAST $documentAST): Schema fn (): array => $this->typeRegistry->possibleTypes(), ); + // Passing scalar overrides explicitly prevents the first lookup of a built-in scalar + // from discovering them by resolving the lazy types callable, which would eagerly + // build every type in the schema, see https://github.com/nuwave/lighthouse/issues/2771. + // TODO remove this check when the minimum version of webonyx/graphql-php includes the method + if (method_exists($config, 'setScalarOverrides')) { + $scalarOverrides = []; + foreach (Type::BUILT_IN_SCALAR_NAMES as $name) { + if (isset($documentAST->types[$name])) { + $type = $this->typeRegistry->get($name); + assert($type instanceof ScalarType); + $scalarOverrides[] = $type; + } + } + + $config->setScalarOverrides($scalarOverrides); + } + // There is no way to resolve directives lazily, so we convert them eagerly $directiveFactory = new DirectiveFactory( new ExecutableTypeNodeConverter($this->typeRegistry), diff --git a/tests/Unit/Schema/SchemaBuilderTest.php b/tests/Unit/Schema/SchemaBuilderTest.php index a9962c8a56..a7db0da6ca 100644 --- a/tests/Unit/Schema/SchemaBuilderTest.php +++ b/tests/Unit/Schema/SchemaBuilderTest.php @@ -8,9 +8,13 @@ use GraphQL\Type\Definition\InputObjectType; use GraphQL\Type\Definition\InterfaceType; use GraphQL\Type\Definition\ObjectType; +use GraphQL\Type\Definition\ScalarType; +use GraphQL\Type\Definition\Type; use Nuwave\Lighthouse\Schema\RootType; use Nuwave\Lighthouse\Schema\SchemaBuilder; +use Nuwave\Lighthouse\Schema\TypeRegistry; use Tests\TestCase; +use Tests\Utils\Scalars\Email; final class SchemaBuilderTest extends TestCase { @@ -209,6 +213,57 @@ public function testExtendTypes(): void $this->assertSame('yo?', $type->getField('bar')->description); } + public function testRegistersExplicitlyEmptyScalarOverrides(): void + { + $schema = $this->buildSchemaWithPlaceholderQuery(''); + + $config = $schema->getConfig(); + if (! method_exists($config, 'getScalarOverrides')) { + $this->markTestSkipped('Requires a version of webonyx/graphql-php that supports SchemaConfig::setScalarOverrides.'); + } + + $this->assertSame([], $config->getScalarOverrides()); + } + + public function testRegistersRedefinedBuiltInScalarsAsScalarOverrides(): void + { + $schema = $this->buildSchemaWithPlaceholderQuery(/** @lang GraphQL */ ' + scalar String @scalar(class: "Email") + '); + + $config = $schema->getConfig(); + if (! method_exists($config, 'getScalarOverrides')) { + $this->markTestSkipped('Requires a version of webonyx/graphql-php that supports SchemaConfig::setScalarOverrides.'); + } + + $scalarOverrides = $config->getScalarOverrides(); + $this->assertNotNull($scalarOverrides); + $this->assertCount(1, $scalarOverrides); + + $stringOverride = reset($scalarOverrides); + $this->assertInstanceOf(Email::class, $stringOverride); + $this->assertSame(Type::STRING, $stringOverride->name); + } + + public function testBuiltInScalarLookupDoesNotResolveAllTypes(): void + { + if (! method_exists(SchemaConfig::class, 'setScalarOverrides')) { + $this->markTestSkipped('Requires a version of webonyx/graphql-php that supports SchemaConfig::setScalarOverrides.'); + } + + $schema = $this->buildSchemaWithPlaceholderQuery(/** @lang GraphQL */ ' + type Foo { + bar: Int + } + '); + + $booleanType = $schema->getType(Type::BOOLEAN); + $this->assertInstanceOf(ScalarType::class, $booleanType); + + $typeRegistry = $this->app->make(TypeRegistry::class); + $this->assertArrayNotHasKey('Foo', $typeRegistry->resolvedTypes()); + } + public function testResolvesEnumDefaultValuesToInternalValues(): void { $schema = $this->buildSchema(/** @lang GraphQL */ <<<'GRAPHQL'