diff --git a/CHANGELOG.md b/CHANGELOG.md index c50455f9d..ec77036e4 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.68.0 ### Added diff --git a/src/Schema/SchemaBuilder.php b/src/Schema/SchemaBuilder.php index b6df5884f..d5bdce439 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 a9962c8a5..a7db0da6c 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'