Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ You can find and compare releases at the [GitHub release page](https://github.co

## Unreleased

### Added

- Allow injecting pre-built type instances when building a schema from SDL https://github.com/webonyx/graphql-php/issues/681

## v15.31.0

### Added
Expand Down
8 changes: 6 additions & 2 deletions docs/class-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -2774,6 +2774,7 @@ See [schema definition language docs](schema-definition-language.md) for details
*
* @param DocumentNode|Source|string $source
* @param array<string, bool> $options
* @param iterable<Type&NamedType> $types
*
* @phpstan-param TypeConfigDecorator|null $typeConfigDecorator
* @phpstan-param FieldConfigDecorator|null $fieldConfigDecorator
Expand All @@ -2791,7 +2792,8 @@ static function build(
$source,
?callable $typeConfigDecorator = null,
array $options = [],
?callable $fieldConfigDecorator = null
?callable $fieldConfigDecorator = null,
iterable $types = []
): GraphQL\Type\Schema
```

Expand All @@ -2805,6 +2807,7 @@ static function build(
* has no resolve methods, so execution will use default resolvers.
*
* @param array<string, bool> $options
* @param iterable<Type&NamedType> $types
*
* @phpstan-param TypeConfigDecorator|null $typeConfigDecorator
* @phpstan-param FieldConfigDecorator|null $fieldConfigDecorator
Expand All @@ -2821,7 +2824,8 @@ static function buildAST(
GraphQL\Language\AST\DocumentNode $ast,
?callable $typeConfigDecorator = null,
array $options = [],
?callable $fieldConfigDecorator = null
?callable $fieldConfigDecorator = null,
iterable $types = []
): GraphQL\Type\Schema
```

Expand Down
12 changes: 11 additions & 1 deletion src/Utils/ASTDefinitionBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,9 +86,13 @@ class ASTDefinitionBuilder
/** @var array<string, array<int, Node&TypeExtensionNode>> */
private array $typeExtensionsMap;

/** @var array<string, Type&NamedType> */
private array $typeOverrides;

/**
* @param array<string, Node&TypeDefinitionNode> $typeDefinitionsMap
* @param array<string, array<int, Node&TypeExtensionNode>> $typeExtensionsMap
* @param array<string, Type&NamedType> $typeOverrides
*
* @phpstan-param ResolveType $resolveType
* @phpstan-param TypeConfigDecorator|null $typeConfigDecorator
Expand All @@ -100,13 +104,15 @@ public function __construct(
array $typeExtensionsMap,
callable $resolveType,
?callable $typeConfigDecorator = null,
?callable $fieldConfigDecorator = null
?callable $fieldConfigDecorator = null,
array $typeOverrides = []
) {
$this->typeDefinitionsMap = $typeDefinitionsMap;
$this->typeExtensionsMap = $typeExtensionsMap;
$this->resolveType = $resolveType;
$this->typeConfigDecorator = $typeConfigDecorator;
$this->fieldConfigDecorator = $fieldConfigDecorator;
$this->typeOverrides = $typeOverrides;

$this->cache = Type::builtInTypes();
}
Expand Down Expand Up @@ -263,6 +269,10 @@ private function internalBuildType(string $typeName, ?Node $typeNode = null): Ty
return $this->cache[$typeName];
}

if (isset($this->typeOverrides[$typeName])) {
return $this->cache[$typeName] = $this->typeOverrides[$typeName];
}

if (isset($this->typeDefinitionsMap[$typeName])) {
$type = $this->makeSchemaDef($this->typeDefinitionsMap[$typeName]);

Expand Down
48 changes: 38 additions & 10 deletions src/Utils/BuildSchema.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use GraphQL\Language\Parser;
use GraphQL\Language\Source;
use GraphQL\Type\Definition\Directive;
use GraphQL\Type\Definition\NamedType;
use GraphQL\Type\Definition\Type;
use GraphQL\Type\Schema;
use GraphQL\Type\SchemaConfig;
Expand Down Expand Up @@ -68,8 +69,12 @@ class BuildSchema
*/
private array $options;

/** @var iterable<Type&NamedType> */
private iterable $types;

/**
* @param array<string, bool> $options
* @param iterable<Type&NamedType> $types
*
* @phpstan-param TypeConfigDecorator|null $typeConfigDecorator
* @phpstan-param BuildSchemaOptions $options
Expand All @@ -78,12 +83,14 @@ public function __construct(
DocumentNode $ast,
?callable $typeConfigDecorator = null,
array $options = [],
?callable $fieldConfigDecorator = null
?callable $fieldConfigDecorator = null,
iterable $types = []
) {
$this->ast = $ast;
$this->typeConfigDecorator = $typeConfigDecorator;
$this->options = $options;
$this->fieldConfigDecorator = $fieldConfigDecorator;
$this->types = $types;
}

/**
Expand All @@ -92,6 +99,7 @@ public function __construct(
*
* @param DocumentNode|Source|string $source
* @param array<string, bool> $options
* @param iterable<Type&NamedType> $types
*
* @phpstan-param TypeConfigDecorator|null $typeConfigDecorator
* @phpstan-param FieldConfigDecorator|null $fieldConfigDecorator
Expand All @@ -109,13 +117,14 @@ public static function build(
$source,
?callable $typeConfigDecorator = null,
array $options = [],
?callable $fieldConfigDecorator = null
?callable $fieldConfigDecorator = null,
iterable $types = []
): Schema {
$doc = $source instanceof DocumentNode
? $source
: Parser::parse($source);

return self::buildAST($doc, $typeConfigDecorator, $options, $fieldConfigDecorator);
return self::buildAST($doc, $typeConfigDecorator, $options, $fieldConfigDecorator, $types);
}

/**
Expand All @@ -127,6 +136,7 @@ public static function build(
* has no resolve methods, so execution will use default resolvers.
*
* @param array<string, bool> $options
* @param iterable<Type&NamedType> $types
*
* @phpstan-param TypeConfigDecorator|null $typeConfigDecorator
* @phpstan-param FieldConfigDecorator|null $fieldConfigDecorator
Expand All @@ -143,9 +153,10 @@ public static function buildAST(
DocumentNode $ast,
?callable $typeConfigDecorator = null,
array $options = [],
?callable $fieldConfigDecorator = null
?callable $fieldConfigDecorator = null,
iterable $types = []
): Schema {
return (new self($ast, $typeConfigDecorator, $options, $fieldConfigDecorator))->buildSchema();
return (new self($ast, $typeConfigDecorator, $options, $fieldConfigDecorator, $types))->buildSchema();
}

/**
Expand Down Expand Up @@ -201,6 +212,18 @@ public function buildSchema(): Schema
'subscription' => 'Subscription',
];

/** @var array<string, Type&NamedType> $typeOverrides */
$typeOverrides = [];
/** @var array<string, Type&NamedType> $extraTypesMap */
$extraTypesMap = [];
foreach ($this->types as $type) {
if (isset($typeDefinitionsMap[$type->name])) {
$typeOverrides[$type->name] = $type;
} else {
$extraTypesMap[$type->name] = $type;
}
}

$definitionBuilder = new ASTDefinitionBuilder(
$typeDefinitionsMap,
$typeExtensionsMap,
Expand All @@ -209,7 +232,8 @@ static function (string $typeName): Type {
throw self::unknownType($typeName);
},
$this->typeConfigDecorator,
$this->fieldConfigDecorator
$this->fieldConfigDecorator,
$typeOverrides
);

$directives = array_map(
Expand Down Expand Up @@ -254,12 +278,16 @@ static function (string $typeName): Type {
->setSubscription(isset($operationTypes['subscription'])
? $definitionBuilder->maybeBuildType($operationTypes['subscription'])
: null)
->setTypeLoader(static fn (string $name): ?Type => $definitionBuilder->maybeBuildType($name))
->setTypeLoader(static fn (string $name): ?Type => $definitionBuilder->maybeBuildType($name)
?? ($extraTypesMap[$name] ?? null))
->setDirectives($directives)
->setAstNode($schemaDef)
->setTypes(fn (): array => array_map(
static fn (TypeDefinitionNode $def): Type => $definitionBuilder->buildType($def->getName()->value),
$typeDefinitionsMap,
->setTypes(fn (): array => array_merge(
array_map(
static fn (TypeDefinitionNode $def): Type => $definitionBuilder->buildType($def->getName()->value),
$typeDefinitionsMap,
),
array_values($extraTypesMap),
))
);
}
Expand Down
66 changes: 66 additions & 0 deletions tests/Utils/BuildSchemaTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
use GraphQL\Language\Parser;
use GraphQL\Language\Printer;
use GraphQL\Tests\TestCaseBase;
use GraphQL\Type\Definition\CustomScalarType;
use GraphQL\Type\Definition\Directive;
use GraphQL\Type\Definition\EnumType;
use GraphQL\Type\Definition\EnumValueDefinition;
Expand Down Expand Up @@ -1452,6 +1453,71 @@ interface Hello {
self::assertSame('My description of Hello', $hello->description);
}

public function testBuildSchemaWithTypeOverrides(): void
{
$sdl = '
schema {
query: Query
}

type Query {
value: MyScalar
status: Status
}

scalar MyScalar

enum Status {
ACTIVE
INACTIVE
}
';

$myScalar = new CustomScalarType([
'name' => 'MyScalar',
'serialize' => static fn ($value) => 'serialized:' . $value,
'parseValue' => static fn ($value) => 'parsed:' . $value,
]);

$myEnum = new EnumType([
'name' => 'Status',
'values' => [
'ACTIVE' => [
'value' => 1,
],
'INACTIVE' => [
'value' => 0,
],
],
]);

$extraType = new ObjectType([
'name' => 'ExtraType',
'fields' => [
'id' => \GraphQL\Type\Definition\Type::string(),
],
]);

$schema = BuildSchema::build($sdl, null, [], null, [$myScalar, $myEnum, $extraType]);

$scalar = $schema->getType('MyScalar');
self::assertSame($myScalar, $scalar);

$enum = $schema->getType('Status');
self::assertSame($myEnum, $enum);

$extra = $schema->getType('ExtraType');
self::assertSame($extraType, $extra);

// Verify the custom scalar serialize/parseValue are actually used
$result = GraphQL::executeQuery(
$schema,
'{ value }',
['value' => 'hello']
);
self::assertSame(['value' => 'serialized:hello'], $result->data);
}

public function testCreatesTypesLazily(): void
{
$sdl = '
Expand Down
Loading